noticeBar的实现

1,122 阅读1分钟

前提概要: 由于element中没有类似于noticeBar的组件,根据公司业务需要,自己实现一个noticeBar,满足需求。

image.png


重点分析

  • 主要使用样式表中inertRule()deleteRule()两个方法,对样式表中的控制动画的@keyframes规则进行新增和删除。 一: 设置@keyframes规则

image.png

  • 第一个动画是控制最开始的状态,contentLength是内容长度,从0开始向左移动,整体移动到消失则需要切换第二个动画。移动总长度为contentLength

image.png

  • 第二个动画是从最右侧(parentWidth)的位置开始出现,到最左边消失。总长度是parentWidth + contentLength

image.png 注意: 需要先删除注册在样式表中的规则,然后再添加新规则。不然在data变化后,contentLength更改,导致样式表中有相同移动长度不同的同名规则,导致速度的变更。速度 = 总长度 / 时间

  • 删除样式表deleteRule(i) iinsertRule()时设置的索引。如果没有设置,默认应该是0.则不好定位,最好在插入样式时设置一个唯一的索引,用来标记删除。

二: 添加动画,需要根据传进来的速度计算时间,然后设置动画。

  • 计算速度设置动画移动时间。时间 = 总长度 / 速度

image.png

注意: 第一个动画只执行一次,第二个动画无限循环。需要监听动画消失事件,切换动画。第一次加载mounted和数据切换都需要。

image.png

整体代码展示

<template>
  <!--主要内容-->
  <div class="noticebar" :style="{backgroundColor: options.background}">
    <div style="margin-left:5px"></div>
    <div ref="parent" class="parent">
      <p ref="content" :style="{fontSize:options.size?options.size:'14px',color:options.color?options.color:'#f60'}" class="content">
        <template v-if="type == 'text'">
          {{ dataText }}
        </template>
        <template v-if="type == 'list'">
          <span class="content-inside" v-for="(n,index) in data" :key="index">
            {{ n.text }}
          </span>
        </template>
      </p>
    </div>
  </div>
</template>
<script>
export default {
    props: {
        options: {
            type: Object,
            default() {
                return {
                    text: '默认'
                };
            }
        },
        type: {
          type: String,
          default: 'text'
        },
        dataText: {
          type: String,
          default: ''
        },
        data: {
          type: Array,
          default() {
            return []
          }
        },
    },
    watch: {
      data: {
        handler: function(data) {
          if (data.length > 0) {
            this.start();
          }
        },
        deep: true
      },
      dataText: {
        handler: function(data) {
          this.start();
        }
      }
    },
    data() {
        return {
            speed: this.options.speed, // 速度(单位px/s)
            parentWidth: '', // 父级宽度
            parentHeight: '', // 父级高度
            contentLength: '', // 文本长度
            state: 1,
            firstAnimationTime: '', // 状态一动画效果
            secondAnimationTime: '', // 状态二动画效果
        };
    },
    methods: {
      start() {
        this.state = 1;
        let content = this.$refs.content;
        // 清空上次的动画
        content.style.animation = '';
        this.Listener();
        this.$nextTick(() => {
          this.setAnimation();
        })
      },
        // 获取数据
        setAnimation() {
          let style = document.styleSheets[0];
          // 切换数据需删除之前的注册事件
          _.filter(style.cssRules, (e,index) => {
            return style.cssRules[index].name === 'firstAnimation' || style.cssRules[index].name == 'secondAnimation'
          }).map(i => {
            style.deleteRule(i);
          })
            let content = this.$refs.content;
            let parent = this.$refs.parent;
            this.parentWidth = parent.offsetWidth;
            this.parentHeight = parent.offsetHeight;
            content.style.lineHeight = this.parentHeight + 'px';
            this.contentLength = content.offsetWidth;
            this.ComputationTime(); // 计算时间
            var s1 = style.insertRule(
                `@keyframes firstAnimation {0%   {left:0px;}100%  {left:-${this.contentLength}px;}}`,
                style.cssRules.length
            );
            var s2 = style.insertRule(
                `@keyframes secondAnimation {0%   {left:${this.parentWidth}px;}100%  {left:-${this.contentLength}px;}}`,
                style.cssRules.length
            );
            setTimeout(res => {
                this.changeState();
            }, this.options.delay);
        },
        // 用速度计算时间(想要保持速度一样,2种状态时间不同需算出)
        ComputationTime() {
            this.firstAnimationTime = this.contentLength / this.speed;
            this.secondAnimationTime = (this.contentLength + this.parentWidth) / this.speed;
        },
        // 根据状态切换动画
        changeState() {
            let content = this.$refs.content;
            if (this.state == 1) {
                content.style.animation = `firstAnimation ${this.firstAnimationTime}s linear`;
                this.state = 2;
            } else {
                content.style.animation = `secondAnimation ${this.secondAnimationTime}s linear infinite`;
            }
        },
        // 监听动画结束事件
        Listener() {
            let content = this.$refs.content;
            content.addEventListener(
                'animationend',
                res => {
                    this.changeState();
                },
                false
            );
        }
    },
    mounted() {
      this.start();
    }
};
</script>
<style lang="scss" scoped>
.noticebar {
    display: flex;
    align-items: center;
    height: 100%;
    width: 100%;
    background-color: #fff7cc;
    .parent {
        overflow: hidden;
        white-space: nowrap;
        margin: 0 auto;
        height: 100%;
        width: 100%;
        position: relative;
        .content {
            position: absolute;
            display: inline-block;
            padding: 2px 0;

            .content-inside {
              display: inline-block;
              margin-right: 50px;
            }
        }
    }
}
</style>