backTop组件(element vs iview)

2,160 阅读2分钟

整体结构

  <transition name="el-fade-in">
    <div
      v-if="visible"
      @click.stop="handleClick"
      :style="{
        'right': styleRight,
        'bottom': styleBottom
      }"
      class="el-backtop">
      <slot>
        <el-icon name="caret-top"></el-icon>
      </slot>
    </div>
  </transition>

可以看到,backtop组件的结构是比较简单的。div > icon
icon是写在slot里面,这样子的写法有一个好处就是如果没有传递slot,那么这个icon将会显示出来,如果传递slot,那么icon将不会渲染,详见官网:cn.vuejs.org/v2/guide/co…

主要功能

backTop组件有两个重要的点

  1. visible: 何时显示
  2. click事件: 点击返回顶部
    如果我们完成这两个功能 ,backTop基本就算完成了。
1. visible
  mounted() {
    this.init();
    this.throttledScrollHandler = throttle(300, this.onScroll);
    this.container.addEventListener('scroll', this.throttledScrollHandler);
  },
  // methods:
    init() {
      this.container = document;
      this.el = document.documentElement;
      if (this.target) {
        this.el = document.querySelector(this.target);
        if (!this.el) {
          throw new Error(`target is not existed: ${this.target}`);
        }
        this.container = this.el;
      }
    },

mounted时,获取proptarget(触发滚动的对象),将target元素赋值给this.elthis.container。监听this.containerscroll事件。同时,这里做了节流的处理。每300毫秒内重复触发scroll事件都会被认为只触发一次 。

我觉得有两个节流与防抖的比喻,非常形象!!!
节流好比地铁限流,过一段时间才会放人进去
防抖好比坐电梯,只有没有人在上来了,电梯门才会关闭。否则持续有人上电梯,电梯是不会关门的。

当滚动的高度大于visibilityHeight(backtop组件可见时的高度), visible才会为true。

2. click

click事件除了emit之外,还要做的就是返回顶部。如果直接将this.el的滚动高度设置为0,当然是最简单最容易达到目的。但是不要忘了,element-ui的设计原则: 一致,反馈,效率,可控。feedback(反馈)这一点,明确的说到:

反馈 Feedback
控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作; 页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。

如果我们能够让滚动条慢慢的滚回到顶部,而不是一下子跳到顶部,是比较符合我们的习惯的。show code:

    scrollToTop() {
      let el = this.el;
      let step = 0;
      let interval = setInterval(() => {
        if (el.scrollTop <= 0) {
          clearInterval(interval);
          return;
        }
        step += 10;
        el.scrollTop -= step;
      }, 20);
    }

attention please:

  • 这里并不是匀速往上滑动,而是越来越快,每隔20ms,scrollTop减去的step都会加10。其实也很好理解,如果我们的页面非常长,滚动条处于底部,我们不希望它像老人车过斑马线一样慢吞吞的过去。
  • 这样子减会有一个问题,如果减到scrollTop < 0呢? 这个时候就要请出我们的MDN规范老师了

scrollTop 可以被设置为任何整数值,同时注意:

  • 如果一个元素不能被滚动(例如,它没有溢出,或者这个元素有一个"non-scrollable"属性), scrollTop将被设置为0。
  • 设置scrollTop的值小于0,scrollTop 被设为0
  • 如果设置了超出这个容器可滚动的值, scrollTop 会被设为最大值.

测试

  it('create', async() => {
    vm = createVue({
      template: `
        <div ref="scrollTarget" class="test-scroll"  style="height: 100px; overflow: auto">
          <div style="height: 10000px; width: 100%">
            <el-backtop target=".test-scroll">
              <span>test_up_text</span>
            </el-backtop>
          </div>
        </div>
      `
    }, true);
    expect(vm.$el).to.exist;
    expect(vm.$el.innerText).to.be.equal('');
    vm.$refs.scrollTarget.scrollTop = 2000;
    await wait();
    expect(vm.$el.innerText).to.be.equal('test_up_text');
  });

测试用例中,对visible进行了测试。这个就比较简单,就无需多讲啦。

iview的异同

iview的代码
不同 element iview
scroll事件 节流 -
滚动对象 prop(target)决定 window
显示隐藏 v-if display
动画 setInterval,逐渐加快 requestAnimationFrame, 匀速
动画的实现

提供了一个prop: duration,是滚动动画持续时间,单位毫秒

// scrollTop animation
export function scrollTop(el, from = 0, to, duration = 500, endCallback) {
    // `requestAnimationFrame `降级处理处理,代码省略
    const difference = Math.abs(from - to);
    const step = Math.ceil(difference / duration * 50);

    function scroll(start, end, step) {
        if (start === end) {
            endCallback && endCallback();
            return;
        }

        let d = (start + step > end) ? end : start + step;
        if (start > end) {
            d = (start - step < end) ? end : start - step;
        }

        if (el === window) {
            window.scrollTo(d, d);
        } else {
            el.scrollTop = d;
        }
        window.requestAnimationFrame(() => scroll(d, end, step));
    }
    scroll(from, to, step);
}
  • requestAnimationFrame降级处理
  • step = Math.ceil(difference / duration * 50)计算每帧需要滚动的距离

为什么是50
我也不知道 = = 我觉得是算错了TAT, 应该是*(1000/60)才对

  • 设置每一帧的scrollTop
  1. 获取滚动的高度方式不同
    iview: window.pageYOffset
    element-ui: el.scrollTop

如图,是在ie8中的使用结果,该用谁我想大家心里清楚吧^_^

总结

结合两个组件库的特点,总结如下:

  1. 使用requestAnimationFrame处理动画
  2. 传递target,指定触发滚动的对象,我们要的backTop组件并非一定是返回document.documentElement的顶部
  3. 滚动事件做节流处理