了解offset、client、scroll和scrollIntoView、scrollTo、scrollBy之间的区别,实现滚动到指定位置、自动滚动。

753 阅读8分钟

前言:在写项目时,偶尔便会遇到需要滚动的情况,比如写大屏时,信息滚动展示等。我记性不太好,时间赖好一长,便会记不清offsetclientscrollscrollIntoViewscrollToscrollBy这之间的区别,那么趁着这次又碰见滚动展示的需求,整理一下,也方便自己遗忘时查阅

1、offset、client、scroll之间的区别

这个就直接上图了,文字描述再多,也不如直接画图说明来的直接
图片来源:offsetHeight,clientHeight,scrollHeight,innerHeight,outerHeight等属性的解释
offsetTop、clientHeight、scrollTop等属性的含义

1.1、offset

offset.png

1.2、client

client.png

1.3、scroll

scroll.png

2、简单布局一块滚动的区域,用于说明测试

<template>
  <div id="content">
    <button @click="onScroll" style="margin-top: 20px;">每次点击,滚动四行距离</button>
    <div id="scrollContainer" class="out">
      <div v-for="items in data" :key="items" 
           :id="items+'data'" class="data"
      >{{ items }}</div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: [],
      num: 0,
      timer: null,
    }
  },
  created() {
    for(let i = 0; i < 200; i++) {
      this.data.push(i+1);
    };
  },
  methods: {
    onScroll() {
      // 每点击一次,向下滚动4行的距离
    },
    autoScroll() {
      // 自动滚动展示
    }
  },
};
</script>
<style scoped>
.out {
  display: flex;
  flex-wrap: wrap;
  width: 400px;
  height: 400px;
  margin: 20px auto 0;
  background: #eee;
  overflow: auto;
}
.data {
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 20%;
  height: 100px;
  border: 1px solid #333;
}
</style>

页面显示如图:

image.png

3、scrollIntoView、scrollTo、scrollBy之间的区别

首先scrollIntoView和scrollTo、scrollBy的调用对象是不一样的
scrollIntoView是滚动容器中的元素去调用:childrenElement.scrollIntoView(参数可选)
scrollTo、scrollBy是滚动容器去调用:parentElement.scrollTo/scrollBy(参数可选)
其次,scrollTo、scrollBy都是滚动到指定位置,但是scrollTo是绝对位置,scrollBy是相对位置,这个光靠文字不好说明,需要结合动图演示,将在下文和参数一起进行具体说明、演示
image.png

参考文章:Element.scrollIntoView()scrollIntoView()方法JS原生滚动到指定元素

3.1、scrollIntoView

childrenElement.scrollIntoView(),将调用它的元素,滚动到可滚动的父元素的可见区域。有两种传参,可以传Boolean或者Object,其中Object包含三个参数:behavior、block、inline

3.1.1、scrollIntoView可传参数说明

参数为Object

参数说明可选值
behavior表示滚动的方式,立即的还是平滑的auto(默认)/instant/smooth(常用)
block垂直方向的对齐方式start(默认)/end/center/nearest
inline水平方向的对齐方式start/end/center/nearest(默认)

参数为Boolean

参数说明对应Object参数
true元素顶端将和其所在滚动区的可视区域顶端对齐block:"start",其余默认值
false元素底端将和其所在滚动区的可视区域底端对齐block: "end",其余默认值

3.1.2、scrollIntoView滚动效果

Boolean:true/false;Object:block:start/end,滚动效果

onScroll() {
    this.num += 21; // 一行5个,要每次向下移动四行,跨越20个数字,+21(为方便,dom id为"数字+data")
    // 获取到滚动容器中的子元素,使调用scrollIntoView的子元素可见
    const ele = document.getElementById(this.num+"data");  
    ele.scrollIntoView(true/false);
    // ele.scrollIntoView({ block: "start"/"end" });
}

下图,第一个图为true(可省略)/block:start,第二个图为false/block:end
booleantrue.gifbooleanfalse.gif

Object:behavior,立即/平滑滚动效果

onScroll() {
    this.num += 21; // 一行5个,要每次向下移动四行,跨越20个数字,+21(为方便,dom id为"数字+data")
    // 获取到滚动容器中的子元素,使调用scrollIntoView的子元素可见
    const ele = document.getElementById(this.num+"data");  
    ele.scrollIntoView({
       behavior: "smooth", // smooth,平滑滚动到可见区域
       block: "start"/"end", // 元素的底端将和其所在滚动区的可视区域的底端对齐,相当于 scrollIntoView(false)
    }); 
}

下图,第一个图为立即滚动,第二个图为平滑滚动
smoothfalse.gifsmoothtrue.gif

我个人常用的这些,其他效果自行尝试

3.2、scrollTo

parentElement.scrollTo()可滚动容器,滚动至绝对指定位置,就是只要位置确定了,不论怎么滑动、从哪开始滑动,都只会滑到这个固定位置。有两种传参,一种直接传(x, y)坐标,一种是传Object,包含三个参数:top、left、behavior

3.2.1、scrollTo可传参数说明

参数为Object

参数说明可选参数
behavior表示滚动的方式,立即的还是平滑的auto(默认)/instant/smooth(常用)
top滚动元素Y轴方向滚动的像素数最小0,最大滚动容器的clientHeight
left滚动元素X轴方向滚动的像素数最小0,最大滚动容器的clientWidth

参数为(x, y)

参数说明对应的Object参数
x滚动元素X轴方向滚动的像素数object的left,最小0,最大滚动容器的clientWidth
y滚动元素Y轴方向滚动的像素数object的top,最小0,最大滚动容器的clientHeight

scrollTo()中,通常使滚动容器滚到最上/左部,将(x, y)或者Objecttop、left都设置成0即可,即使给上负值,浏览器也会再设置成0,所以最小就是0;通常判断是否滚到最底部了,是用“scrollHeight - scrollTop === clientHeight”去判断的(原因查看上方的offset、client、scroll区别),所以能滚动的最大值就是clientHeight/clientWidth,即使给上无穷大,浏览器也会再设置成这个最大值

3.2.2、scrollTo滚动效果

this.num += 4; // 每次点击,滚动四行距离
const ele = document.getElementById("scrollContainer");
ele.scrollTo({
  top: 400 * 0.25 * this.num, // 400,滚动容器高度;0.25是每行高度(400×25%);
  behavior: "smooth"
});

与上文的ele.scrollIntoView({behavior: "smooth", block: "start"})效果一致
tosmoothtrue.gif

其他效果可以自行尝试

3.3、scrollBy

parentElement.scrollBy()可滚动容器,滚动至相对现在位置的指定位置,即从现在的位置在X/Y轴上滚动指定像素的距离。参数与scrollTo一致,只是scrollBy参数可以为负值,正值是向下/右移动,直到最下/右部,负值是向上/左移动,直到最上/左

3.4、scrollTo和scrollBy滚动效果对比

const ele = document.getElementById("scrollContainer");
ele.scrollTo({
  top: 90,
  behavior: "smooth"
});

const ele = document.getElementById("scrollContainer");
ele.scrollby({
  top: 90,
  behavior: "smooth"
});

点击按钮,向下滚动90像素距离,以此比较
下图中,第一个图为scrollTo的滚动效果,第二个图为scrollby的滚动效果
scrollTo.gifscrollBy.gif
可以发现,top固定时,scrollTo只会滚动到这个固定位置,不论怎么点击,都不会有变化;而scrollby每按一次就会滚动一个相对当前位置的固定距离。这就是上面所我表达的“绝对位置”和“相对位置

4、自动滚动

在写项目时,尤其是大屏,经常会碰见,让某些东西自动循环滚动,去展示信息。下面就简单说一下自动循环滚动的方法,包括无缝循环滚动

4.1、使用scrollTop实现自动滚动

autoScroll() {
  const ele = document.getElementById("scrollContainer");
  this.timer = setInterval(() => {
      // 每20毫秒向下移动10个像素,速度设置较快,主要是快速滚动下方,方便演示
      ele.scrollTop += 10;
      if(ele.scrollHeight - ele.scrollTop === ele.clientHeight) { // 判断是否滚到最底部
          clearInterval(this.timer);
          this.timer = null;
          setTimeout(() => {
            ele.scrollTop = 0; // 最底部停留2秒回到最开始
            this.autoScroll(); // 然后重新开始滚动
          }, 2000)
      };
  }, 20);
}

  scrollTopauto.gif

4.2、自动无缝循环滚动

无缝循环,基本逻辑是:
1:复制一份一模一样的出来放到后面;
2:当第一份正文内容已经全部滚动显示出来后,再继续滚动就是显示的第二份复制的内容,既然是复制的,那必然内容与正文一模一样,因此也就看不出来是新的内容在滚动;
3:当第一份的正文内容完全滚动完毕后,即第一份的正文内容已经全部滚出可见区域;
4:再将scrollTop设置为0,重头开始滚动,从而实现无缝循环。下图示意:
image.pngimage.pngimage.pngimage.png

2、简单布局一块滚动的区域,用于说明测试中的代码,做如下修改:

// template中修改,“复制一份”一模一样的内容,放到后面
<div id="content">
  <div id="scrollContainer" class="out">
    <div id="scroll1">
      <div v-for="items in data" :key="items+Math.random()" class="data">
        {{ items }}
      </div>
    </div>
    <div id="scroll2">
      <div v-for="items in data" :key="items+Math.random()" class="data">
        {{ items }}
      </div>
    </div>
  </div>
</div>

// 样式修改如下,删掉out的felx布局,添加scroll1、scroll2
.out {
  width: 400px;
  height: 400px;
  background: #eee;
  overflow: auto;
  margin: 20px auto 0;
  border: 1px solid #333;
}
#scroll1,
#scroll2 {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
}

// 为了方便测试,能快速滚动到底,循环添加的数字由200个改为了50个
for(let i=0;i<50;i++) {
  this.data.push(i+1)
}

自动循环滚动:

autoScroll() {
  const ele = document.getElementById("scrollContainer");
  setInterval(() => {
    ele.scrollTop += 2
    // 判断第一份内容是否完全滚动完毕。如果一份内容的scrollHeight是1,那两份张自然是2
    // 所以当scrollHeight×0.5<=scrollTop时,即第一份内容完全滚动完毕,即全部滚出可视区域
    // 不用==判断是因为,滚动速度不同,两者不一定能正好相等
    if(ele.scrollHeight * 0.5 <= ele.scrollTop) {
      // ele.scrollTop = 0; 如果可以让scrollHeight×0.5和crollTop相等,可直接设置成0
      ele.scrollTop = ele.scrollTop - ele.scrollHeight * 0.5;
    }
  }, 20);
},

效果如图,注意观察滚动条变化:
滚动.gif

4.2.1、隐藏滚动条

为了视觉效果更好,可以使用::-webkit-scrollbar{ display:none; } 隐藏滚动条

.out::-webkit-scrollbar{
  display:none;/*隐藏滚动条*/
} 
// 兼容火狐浏览器和ie
.out{
  scrollbar-width: none; // 兼容火狐浏览器
  -ms-overflow-style: none; // 兼容ie
}

参考文章:css隐藏元素滚动条兼容各大浏览器css怎么隐藏滚动条
无滚动条.gif

5、扩展:requestAnimationFrame请求动画帧

参考文章:一文详解requestAnimationFrame请求动画帧

requestAnimationFrame的用法与setTimeoutsetInterval很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行。

autoScroll() {
  const ele = document.getElementById("scrollContainer");
  const scrollFun = () => {
    ele.scrollTop += 2
    if(ele.scrollHeight * 0.5 <= ele.scrollTop) { 
      ele.scrollTop = 0;
    }
    requestAnimationFrame(scrollFun)
  }
  requestAnimationFrame(scrollFun)
},

  requestAnimationFrame.gif