react 公告栏组件+定时器写法优化

Notice.tsx文件:

import { BellOutlined, NotificationOutlined } from '@ant-design/icons';
import React, { useEffect, useState } from 'react'
import styles from './xxx.module.css';// css文件自行命名

interface IProps {
    data: IObj;
    onClickNoticeDetail?:(item, index) => any;
}
interface IObj {
    noticeList: INoticeList[];
    animate: boolean;
}
interface INoticeList {
    key: number|string;
    text: string;
    detailText?: string|null;
}

export const Notice = (props: IProps) => {
    const {onClickNoticeDetail, data} = props;
    let [obj, setObj] = useState<any>(data);
 //页面加载的时候,设置一个永恒的定时器,1.5s后就会执行定时器中的逻辑
    useEffect(()=>{
        setTimeout(() => {
            obj['animate'] = true
            changeAnim()
        }, 2000);
    }, [obj])
 
 //在setInterval执行中,会调用该函数,在内部会设置一个一次性的定时器,每次都会将数组的第一个元素添加到数组的最后,并且将数组的第一个元素删除,
    const changeAnim = () => {
        const { noticeList } = obj
        setTimeout(() => {
            const noticeList1 = JSON.parse(JSON.stringify(noticeList))
            noticeList1.push(noticeList1[0]);
            noticeList1.shift();
            setObj({
                ...obj,
                noticeList: noticeList1,
                animate: false
            })
        }, 1500)
    }


    const renderContent = () =>  {
        const { noticeList, animate } = obj;
        return (
            <div className={styles["scrollPage"]}>
                <div className={styles["scrollWrapper"]}>
                    <ul className={animate ? styles['anim'] : ''}>
                        {
                            noticeList.map((item, index) => {
                                return <li key={index}>
                                <span onClick={()=>{
                                    onClickNoticeDetail&&onClickNoticeDetail(item, index)
                                }}><NotificationOutlined translate={undefined}/></span>
                                <span className="text">{item.text}</span>
                                {/* <span><BellOutlined translate={undefined} /><i>222222</i></span> */}
                                </li>
                            })
                        }
                    </ul>
                </div>
            </div>
        )
    }

    return renderContent()
}

css文件:

.scrollPage {
  margin: 0px;
  z-index: 999999999999999999999;
    height: 100px;
    position: fixed;
    top: 0;
  .scrollWrapper {
    position: relative;
    height: 24px;
    line-height: 24px;
    overflow: hidden;
    }
    & ul {
      position: absolute;
      top: 20px;
      left: 0;
      height: 24px;
      overflow: hidden;
      left: 400px;
      box-sizing: border-box;
    }
    &  li {
        list-style: none;
        height: 24px;
        line-height: 24px;
        display: flex;
        width: 800px;
    }
    & li>span:first-child,
    & li>span:nth-child(3) {
        display: inline-block;
        font-size: 24px;
    }
    & li>span:nth-child(3) {
        position: relative;
        flex: 0 0 100px;
    }
    & li>span:nth-child(3)>i {
        display: inline-block;
        min-width: 20px;
        height: 14px;
        line-height: 12px;
        border-radius: 5px;
        position: absolute;
        left: 10px;
        font-size: 12px;
        color: #fff;
    }
    & li>span:nth-child(2) {
        display: inline-block;
        flex:1;
        white-space:nowrap; 
        overflow:hidden; 
        text-overflow:ellipsis;
    }
}

.anim {
  transition: all .5s;
  margin-top: 24px;
}

模拟数据及引用:

let obj1 = {
  noticeList: [
      {
          key: 1,
          text: '11iPhone11挥泪降价1600元 iPhone12出道即巅峰?5G手机11',
          detailText: '11'
      },
      {
          key: 2,
          text: '22R式体验奔驰博物馆重新开张 广东最惨的"88888"车牌VR式体验奔驰博物馆重新开张 VR式体验奔驰博物馆重新开张 广东最惨的"88888"车牌VR式体验奔驰博物馆重新开张 广东最惨的"88888"车牌22222222222222222222222222222222222222244444444444444444',
          detailText: '22'
      },
      {
          key: 3,
          text: '334年5队的落选秀太香了 巅峰2.6帽!力压魔兽夺最佳新秀33',
          detailText: '33'
      },
      {
          key: 4,
          text: '44你好世界:寻找心中的风景 [征集]寻找中式风景禅意美44',
          detailText: '44'
      },
  ],
  animate: false,
}

<Notice data={obj1} onClickNoticeDetail={(item,index)=>{
        showMsgInfo(JSON.stringify(item));
      }}/>

如果需要点击通知图标时停止定时器:

引入文件--略

interface IProps {
    data: IObj;
    onClickNoticeDetail?: (item, index, fn) => any;
}
interface IObj {
    noticeList: INoticeList[];
    animate: boolean;
}
interface INoticeList {
    key: number | string;
    text: string;
    detailText?: string | null;
}

export const Notice = (props: IProps) => {
    const { onClickNoticeDetail, data } = props;
    let [obj, setObj] = useState<any>(data);
    let [timerList, setTimerList] = useState<any>([]);
    let [timer] = useState<any>(null);
    let [noticeFlagStorage] = useState<any>({
        cancelToken2: 'Y'
    });

    //页面加载的时候,设置一个永恒的定时器
    useEffect(() => {
        intervalStart();
    }, [obj])

    const intervalStart = () => {
        if (timer) {
            clearTimeout(timer);
            timer = null;
        }
        noticeFlagStorage.cancelToken2 = 'Y';
        timer = int1();
        timerList.push(timer);
    }

    const intervalEnd = () => {
        if (timer || timerList.length > 0) {
            console.log('=============结束了222222222==========');
            each(timerList, (item, index) => {
                clearTimeout(item);
            });
            setTimerList([]);
            clearTimeout(timer);
            timer = null;
        }
    }

    const int1 = () => {
        if ('cancelToken2' in noticeFlagStorage) {
            return setTimeout(() => {
                console.log('开始了2222-----', timerList.length)
                obj['animate'] = true;
                if (noticeFlagStorage.cancelToken2 === 'N') {
                    intervalEnd();
                }
                else {
                    changeAnim()
                }
            }, 2000);
        }
        else {
            intervalEnd();
            return null;
        }
    }

    //在定时器执行中,会调用该函数,在内部会设置一个一次性的定时器,每次都会将数组的第一个元素添加到数组的最后,并且将数组的第一个元素删除,
    const changeAnim = () => {
        setTimeout(() => {
            const { noticeList } = obj;
            noticeList.push(noticeList[0]);
            noticeList.shift();
            setObj({
                ...obj,
                noticeList,
                animate: false
            })
        }, 0);
    }

const renderContent = () =>  {
        const { noticeList, animate } = obj;
        return (
            <div className={styles["scrollPage"]}>
                <div className={styles["scrollWrapper"]}>
                    <ul className={animate ? styles['anim'] : ''}>
                        {
                            noticeList.map((item, index) => {
                                return <li key={index}>
                                <span onClick={()=>{
                                    noticeFlagStorage.cancelToken2 = 'N'
                                    onClickNoticeDetail&&onClickNoticeDetail(item, index, intervalStart)
                                }}><NotificationOutlined translate={undefined}/></span>
                                <span className="text">{item.text}</span>
                                </li>
                            })
                        }
                    </ul>
                </div>
            </div>
        )
    }

    return renderContent()
}

使用: 点击弹窗确认按钮继续定时器

<Notice data={obj1} onClickNoticeDetail={(item,index, intervalStart)=>{
        Modal.success({
            mask: false,
            okText: '确认',
            content: '公告内容',
            centered: true,
            onOk: () => {
              showMsgInfo(JSON.stringify(item));
              intervalStart()
            },
          });
      }}/>

但有的公告栏是只展示第一个, 循环跑马灯式播放:

css: 

@keyframes wordsloop {
    0% {
      transform: translateX(0px);
    }
    100% {
      transform: translateX(-100%);
    }
  }
& li>p>span {
      height: 24px;
      line-height: 24px;
      background: rgba(0,0,0,0);
      border: 0;
      display: inline-block;
      flex:1;
      white-space: nowrap;
      width: auto;
      margin-left: 10px;
      font-size: 14px;
      color: #333;
  }

tsx文件:

// 根据公告内容的所在的标签的宽度与其父标签比较, 设置宽度; 传入动画时间比写在css中可控
useEffect(() => {
        if (get(ref1, 'children[0].scrollWidth') + 10 < ref1.clientWidth/2) {
            setStyle1({
                width: `${get(ref1, 'clientWidth')}px`,
                minWidth: `${get(ref1, 'clientWidth')}px`,
                animation: `${styles['wordsloop']} ${animationDuration}s linear infinite normal`,
            })
        }
        else {
            setStyle1({
                width: 'auto',
                minWidth: 'auto',
                animation: `${styles['wordsloop']} ${animationDuration}s linear infinite normal`,
            })
        }
    }, [ref1, animationDuration])

// 公告栏标签部分代码,其他略...
<div className={styles["scrollPage"]}>
                    <div className={classNames(styles["scrollWrapper"], appear === 'solidBlue' ? styles.scrollWrapperBlue : '')}>
                        <ul>
                            {
                                noticeList.map((item, index) => {
                                    return <li key={index} onClick={() => {
                                        clickNotice(item, index, undefined);
                                    }}>
                                        <span>
                                            <i style={appear === 'solidBlue' ? {
                                                filter: 'drop-shadow(40px 0 0 #007aff)',
                                                transform: 'translateX(-40px)'
                                            } : {}}></i>
                                        </span>
                                        <p ref={ref => setRef1(ref)}>
                                            <span style={style1}>{item.CONTENT}</span>
                                            <span style={style1}>{item.CONTENT}</span>
                                        </p >
                                    </li>
                                })
                            }
                        </ul>
                    </div>
                </div>

还有一个需要优化的地方: 当页面纵向滚动时因为position是fixed的缘故,一直停留在固定位置, 如果需要随着滚动平移到正确位置, 可以监听滚动事件解决:

const handleScroll = (event: any = {}) => {
        // 滚动的高度
        const scrollTop: number =
            (event?.srcElement ? event?.srcElement?.scrollTop : 0) ||
            window.pageYOffset ||
            (event?.srcElement ? event?.srcElement?.body?.scrollTop : 0) ||
            0
        if (scrollTop >= 0) {
            setStyle0({
                transform: `translateY(-${scrollTop}px)`
            })
        }
    }

useEffect(() => {
        window.addEventListener('scroll', handleScroll, true); // 滚动事件
    }, [window]);


// 公告栏标签
<div className={classNames(styles["scrollPage"], appear === 'solidRed' ? styles['scrollPageRed'] : '')} style={style0}>
                    <div className={classNames(styles["scrollWrapper"], appear === 'solidBlue' ? styles.scrollWrapperBlue : '')}>
                        <ul>
                            {
                                noticeList.map((item, index) => {
                                    return <li key={index} onClick={() => {
                                        clickNotice(item, index, undefined);
                                    }}>
                                        <span>
                                            <i style={appear === 'solidBlue' ? {
                                                filter: 'drop-shadow(40px 0 0 #007aff)',
                                                transform: 'translateX(-40px)'
                                            } : {}}></i>
                                        </span>
                                        <p ref={ref => setRef1(ref)}>
                                            <span style={style1}>{item.CONTENT}</span>
                                            <span style={style1}>{item.CONTENT}</span>
                                        </p >
                                    </li>
                                })
                            }
                        </ul>
                    </div>
                </div>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值