其他分享
首页 > 其他分享> > 数值滚动效果实现方案,支持可视范围判断

数值滚动效果实现方案,支持可视范围判断

作者:互联网

file

本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注、转载。

需求分析:

实现方案:

样式

/* html */
  <div class="_single-digi-scroller">
    <div class="placeholder" ref="myPlaceHolder">0</div>
    <div class="display-panel" ref="myDigiPanel">
      <div class="num">0</div>
      <div class="num">1</div>
      <div class="num">2</div>
      <div class="num">3</div>
      <div class="num">4</div>
      <div class="num">5</div>
      <div class="num">6</div>
      <div class="num">7</div>
      <div class="num">8</div>
      <div class="num">9</div>
    </div>
  </div>

/* style */
._single-digi-scroller {
  display: inline-block;
  position: relative;
  overflow: hidden;
  .placeholder {
    opacity: 0;
  }
  .display-panel {
    position: absolute;
  }
}

滚动逻辑

  props: {
    from: {
      type: [Number, String],
      default: 0
    },
    to: {
      type: [Number, String],
      default: 0
    },
    height: {
      type: [Number, String],
      default: 0
    },
    speed: {
      type: [Number, String],
      default: 2
    }
  },
  data: () => ({
    toPos: false,
    fromPos: false,
    transitionStyle: {}
  }),
  watch: {
    changeInfo: {
      immediate: true,
      handler(val) {
        if (val) {
          this.fromPos = { top: `-${val.from * this.height}px` };
          setTimeout(() => {
            // 不用nexttick是因为其间隔太小,浏览器未渲染就改变了pos,导致动画没生效
            this.toPos = { top: `-${val.to * this.height}px` };
            this.transitionStyle = { transition: `${this.speed}s` };
          }, 200);
        }
      }
    }
  },
  computed: {
    numStyle() {
      return {
        height: `${this.height}px`,
        lineHeight: `${this.height}px`
      };
    },
    panelStyle() {
      if (this.toPos) return { ...this.toPos, ...this.transitionStyle };
      if (this.fromPos) return { ...this.fromPos, ...this.transitionStyle };
      return {};
    },
    changeInfo() {
      if ((this.from || this.from === 0) && (this.to || this.to === 0)) {
        return { to: this.to, from: this.from };
      }
      return false;
    }
  }
    <div class="display-panel" :style="panelStyle" ref="myDigiPanel">
      <div class="num" :style="numStyle">0</div>
      <div class="num" :style="numStyle">1</div>
      ...
    </div>

组装

/* html */
  <div class="_digi-scroller" 
    :style="{ height: `${height}px`, width: `${ changeInfo ? width * changeInfo.to.length : 0}px` }"
    ref="myDigiScroller"
  >
    <div class="placeholder" ref="myPlaceHolder">0</div>
    <div :style="{ left: `-${width}px` }" class="digi-zone" v-if="changeInfo">
      <single-digi-scroller
        class="single-digi"
        v-for="(digi, index) in changeInfo.to"
        :key="changeInfo.to.length - index"
        :from="changeInfo.from[index]"
        :to="digi"
        :height="height"
      />
    </div>
  </div>

/* style */
._digi-scroller {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  .placeholder {
    display: inline-block;
    opacity: 0;
  }
  .digi-zone {
    position: relative;
    display: inline-block;
  }
}
  mounted() {
    const { clientHeight, clientWidth } = this.$refs.myPlaceHolder;
    this.height = clientHeight;
    this.width = clientWidth;
  },
  props: {
    from: {
      type: [Number, String],
      default: 0
    },
    to: {
      type: [Number, String],
      default: 0
    }
  },
  computed: {
    changeInfo() {
      if ((this.from || this.from === 0) && (this.to || this.to === 0)) {
        // from 和 to 都接收到了之后
        const len = Math.max(String(this.to).length, String(this.from).length);
        // eslint-disable-next-line prefer-spread
        const from = `${Array.apply(null, Array(len)).map(() => '0').join('')}${this.from}`.slice(-len);
        // eslint-disable-next-line prefer-spread
        const to = `${Array.apply(null, Array(len)).map(() => '0').join('')}${this.to}`.slice(-len);
        return { from, to };
      }
      return false;
    }
  },

可视范围判断

  checkObserverSupport() {
    return 'IntersectionObserver' in window
     && 'IntersectionObserverEntry' in window
     && 'intersectionRatio' in window.IntersectionObserverEntry.prototype;
  }
  data: () => ({
    height: 0,
    width: 0,
    inView: false,
    listener: undefined,
    scrollTimer: undefined // 滚动监听节流用
  })
  checkIntoView() {
    if (this.checkObserverSupport()) {
      // eslint-disable-next-line no-unused-expressions
      this.listener && this.listener.disconnect();
      this.listener = new IntersectionObserver(
        entries => {
          const { intersectionRatio } = entries[0];
          if (intersectionRatio > 0) {
            this.listener.disconnect();
            console.log('intersection observer: digi scroller into view');
            this.listener = undefined;
            this.inView = true;
          }
        }
      );
      this.listener.observe(this.$refs.myDigiScroller);
    } else if (this.checkRectBounding()) {
      this.inView = true;
    } else {
      if (!this.scrollContainer) {
        this.scrollListenContainer = window;
      } else if (typeof this.scrollContainer === 'string') {
        this.scrollListenContainer = document.querySelector(this.scrollContainer);
      } else {
        this.scrollListenContainer = this.scrollContainer;
      }
      this.scrollListenContainer.removeEventListener('scroll', this.checkIntoViewPollyfill);
      this.scrollListenContainer.addEventListener('scroll', this.checkIntoViewPollyfill);
    }
  }

/* computed */
  changeInfo() {
    if ((this.from || this.from === 0)
      && (this.to || this.to === 0)
      && this.inView // 只有this.inView为true时才会开始计算
    ) {
      ...
    }
  }
  checkRectBounding() {
    if (!this.$refs.myDigiScroller) return false;
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
    const rect = this.$refs.myDigiScroller.getBoundingClientRect() || {};
    const { top } = rect;
    return +top <= viewPortHeight + 100; // 由于存在节流,这里判定范围扩大一点
  }
  checkIntoViewPollyfill() {
    if (this.scrollTimer) return;
    const isInView = this.checkRectBounding();
    this.scrollTimer = setTimeout(() => { this.scrollTimer = undefined; }, 100); // 节流
    if (isInView) {
      console.log('scroll listener: digi scroller into view');
      this.scrollListenContainer.removeEventListener('scroll', this.checkIntoViewPollyfill);
      // eslint-disable-next-line no-unused-expressions
      this.scrollTimer && clearTimeout(this.scrollTimer);
      this.scrollTimer = undefined;
      this.inView = true;
    }
  }
  beforeDestroy() {
    if (this.listener) {
      this.listener.disconnect();
      this.listener = undefined;
    }
    if (this.scrollListenContainer) {
      this.scrollListenContainer.removeEventListener('scroll', this.checkIntoViewPollyfill);
    }
    // eslint-disable-next-line no-unused-expressions
    this.scrollTimer && clearTimeout(this.scrollTimer);
    this.scrollTimer = undefined;
  }

结语

欢迎大家留言讨论,祝工作顺利、生活愉快!

我是bigo前端,下期见。

标签:滚动,数字,可视,数值,height,组件,return
来源: https://blog.csdn.net/yeyeye0525/article/details/120412500