Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

记录第一个vue组件 Backtop:2020-10-29 #22

Open
jsonz1993 opened this issue Jul 27, 2021 · 0 comments
Open

记录第一个vue组件 Backtop:2020-10-29 #22

jsonz1993 opened this issue Jul 27, 2021 · 0 comments

Comments

@jsonz1993
Copy link
Owner

本文为博客迁移过来,原文链接: 记录第一个vue组件 Backtop:2020-10-29

Backtop 作为一个不起眼的小组件,api也不会很多,最适合拿来当上手的小组件,这里是基于vue写的组件。

在写一个组件之前,我们要先考虑这个组件的api都有哪些,先把 props 列好,再规划组件怎么写就事半功倍

在做之前也参考了一些业界比较有名的组件库,比如 ant-designelement-ui

  • target 触发滚动的对象 默认是 document.documentElement
  • visible 是否显示,如果有传值则组件为受控组件,否则根据其他条件来展示(滚动的高度)
  • visiblityHeight 滚动高度的阈值,当监听的对象滚动超过这个值时才出现回到顶部的按钮
  • right, bottom, className 按钮的样式配置
  • slot 自定义回到顶部的dom结构

上面这些props都很好实现,这个组件的灵魂其实就只有 scrollTo 这个功能

ScrollTo

我们可以单独实现一个scrollTo的公共函数,后面其他组件可以直接调用

scrollBehavior

在一般的情况下,如果只是默认回到顶部,没有滚动时间需求的话,用系统的api是最优选择MDN scroll-behavior

// 先判断是否支持该属性,不支持的话,会直接跳到目标位置没有动画
const isSuperScrollBehavior = 'scrollBehavior' in target.style
isSuperScrollBehavior && target.scrollTo({
  top,
  behavior: 'smooth'
})

requestAnimationFrame

如果浏览器不支持(safari)或者我们需要控制滚动的时间怎么办呢?
这时候我们就得手动计算当前滚动值,然后赋值给 target.scrollTop 以达到滚动的效果
一般这种模拟动画执行我们都会用 rAF(requestAnimationFrame),对于不支持rAF的浏览器直接简单粗暴用 setTimeout(cb, 16) 来代替。

滚动的动画我选的是 easeInOutCubic,关于其他的贝塞尔曲线实现可以看这个仓库bezier-easing

最后再注意一下重复调用scrollTo的绑定问题就可以了

const sign = Symbol('elementScrollToEvent'); // 这里可以直接项目的规范不用Symbol

// 缓动函数
const easeInOutCubic = t => t < .5
    ? (t ** 3) * 4
    : (t - 1) * ( 2 * t - 2) ** 2 + 1

const getScroll = (target, isTop) => {
  const isWindow = target === window;
  const props = isTop ? 'pageYOffset' : 'PageXOffset';
  const methods = isTop ? 'scrollTop' : 'scrollLeft';

  return target[isWindow ? props : methods];
};

function scrollTo(target, { top = 0, time } = {}) {
  const superScrollBehavior = 'scrollBehavior' in target.style;

  if (time || !superScrollBehavior) {
    const startTime = Date.now();
    const startScrollTop = getScroll(target, true);
    const totalOffset = startScrollTop - top;

    if (target[sign]) {
      window.cancelAnimationFrame(target[sign]);
    }

    const frameFunc = () => {
      const progress = (Date.now() - startTime) / (time || 450);

      if (progress < 1) {
        target.scrollTop = startScrollTop - totalOffset * easeInOutCubic(progress);
        window.requestAnimationFrame(frameFunc);
      } else {
        target.scrollTop = top;
      }
    };

    target[sign] = window.requestAnimationFrame(frameFunc);
  } else {
    target.scrollTo({
      top,
      behavior: 'smooth'
    });
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant