JS滑动滚动的n种方式

# 阅读本文,你将:

  1. 了解原生JS实现页面滚动的多种方式
  2. 了解这多种方式可以对应上的效果以及推荐的应用场景
  3. 多个滑动方面的坑以及相应(如果有)的解决方案
  4. 获得一些有用的函数

实现汇总

1 scrollIntoView

1.1 基本用法
let element = document.getElementById("scrollView");
 
element.scrollIntoView();
element.scrollIntoView(false);
element.scrollIntoView({block: "end"});

scrollIntoView对页面元素调用,会滚动元素的父容器,将该元素滚动到浏览器的可视区域

这是对hash锚点定位的进化升级,对于常用框架由于使用了hashRouter导致锚点定位失效的情况是一种不错的补偿

1.2 API介绍

alignToTop可选

一个Boolean值:

  • 如果为true,元素的顶端将和其所在滚动区的可视区域的顶端对齐。相应的 scrollIntoViewOptions: {block: "start", inline: "nearest"}。这是这个参数的默认值。
  • 如果为false,元素的底端将和其所在滚动区的可视区域的底端对齐。相应的scrollIntoViewOptions: {block: "end", inline: "nearest"}

scrollIntoViewOptions 可选

一个包含下列属性的对象:

  • behavior 可选
    定义动画过渡效果, "auto""smooth" 之一。默认为 "auto"
  • block 可选
    定义垂直方向的对齐, "start", "center", "end", 或 "nearest"之一。默认为 "start"
  • inline 可选
    定义水平方向的对齐, "start", "center", "end", 或 "nearest"之一。默认为 "nearest"
1.3 浏览器的支持度

js ios 滑动不流畅_开发语言

1.4 示例与推荐使用场景
  • 原始位置图

js ios 滑动不流畅_js ios 滑动不流畅_02

我们定位如图红框的元素,从顶部开始示例

1.4.1 scrollIntoVIew()

js ios 滑动不流畅_工作区_03

页面滑动至父级容器,但是子项并没有被滑动到可视区域

1.4.2 scrollIntoView(false)

js ios 滑动不流畅_js ios 滑动不流畅_04

页面滑动到底部,该元素从下方进入可视区域

1.4.3 scrollIntoView({block:“center”,inline:“center”})

js ios 滑动不流畅_开发语言_05

仍然是没有看到我们要呈现的元素,疑似原因为我们的选定元素的爷爷级元素才是可滑动的

1.5 补充
  1. scrollIntoViewscrollIntoViewIfNeeded的区别在于,第一前者的支持性较高,后者则仍是非标准的。
  2. 功能上则是,后者如果该元素已经在浏览器窗口的可见区域内,则不会发生滚动。
  3. 有出现父容器滑动不到指定地方的问题,可以两次调用api(注意异步)

2 设置scrollTop/scrollLeft

2.1 基本用法
element.scrollTop=100;

当我们获取到一个元素,直接设置它的scrollTop即可

2.2 scrollTop的坑,请仔细阅读

scrollTop是什么?为什么我们设置scrollTop总是无效。在使用scrollTop之前我们必须先了解scrollTop是什么。

根据MDN上的定义可知

Element.scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。

一个元素的 scrollTop 值是这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为0

那么,如果一个元素没有滚动条,采用的是transform模拟滚动,那么就无效咯

2.3 适用性检测

可以用下边的代码在控制台检测一下页面有没有任何一个地方的代码scrollTop不是0的

let elementList=[]
const elementBy=(start)=>{
    if(!start?.children || start?.children?.length===0){
        return null
    }
    for(let i=0;i<start.children.length;i++){
      	elementBy(start.children[i]);
    }
    if(start.scrollTop!=0){
        elementList.push(start)
    }

}
elementBy(document.body);
console.log(elementList);

js ios 滑动不流畅_前端_06

能找到你希望的元素才适用scrollTop设置滑动条

2.4 使用示例

原位置

js ios 滑动不流畅_js ios 滑动不流畅_07

elementList[0].scrollTop=100

js ios 滑动不流畅_javascript_08

可以看到明显的,符合预期的scrollview区域滑动

3 window.scrollToelement.scrollTo

3.1 基本用法
window.scrollTo({
  left:0,
  top:0,
  behavior:'smooth'//或'auto'
}); //有效
window.scrollTo(0,0) //有效

上述两种参数形式都有效,作用是返回到顶部。

区别是设置behavior为’smooth’后会平滑滚动

3.2 使用说明

如果场景要求我们滚动页面到某个元素的位置,此时可以使用window.scrollTo();

如果场景要求我们滚动到某个可滚动父元素的位置,此时可以使用element.scrollTo();

相比较于上边的scrollIntoView,我们可以更自由的控制元素显示的位置

3.3 补充
  1. 设置横坐标无效的情况请确定下方出现了横向滚动条,即页面宽度需要大于浏览器宽度
  2. 常见误解:element.scrollTo并不是将某个元素滚动到页面某个位置,而是如果该元素可滚动,设置该元素的滚动条

4 window.scrollByelement.scrollBy

4.1 基本用法

啊哈,这个api一看就是element.scrollTo的近亲

实际功能体现上同样如此,该api用于相对滚动

对比window.scrollTo的话:

window.scrollTo(x(),y());
//等同于
window.scrollLeft=x();
window.scrollTop=y();

//而scrollBy则:
window.scrollBy(10,20);
window.scrollLeft+=10;
window.scrollTop+=20;
4.2 其他

同上方的scrollTo

5 window.scrollelement.scroll

滚动相关的api还包含这位,不过经查,该api和scrollTo效果完全相同


接下来是没什么用系列滚动api

6 window.scrollByLines

该api仅FireFox支持

6.1 基本用法

表示相对当前的滚动位置再滚动指定行数距离,行为表现接近于上下键控制滚动

例如window.scrollByLines(-5)表示向上滚动5行

7 window.scrollByPages

该api仅FireFox支持

7.1 基本用法

表示相对当前的滚动位置再滚动指定页数距离,行为表现接近于空格键(反向+Shift)控制滚动

例如window.scrollByPages(-1)表示向上滚动1页的距离

扩展

1 找到页面内哪个元素的scrollTop不为0

let elementList=[]
const elementBy=(start)=>{
    if(!start?.children || start?.children?.length===0){
        return null
    }
    for(let i=0;i<start.children.length;i++){
      	elementBy(start.children[i]);
    }
    if(start.scrollTop!=0){
        elementList.push(start)
    }

}
elementBy(document.documentElement);
console.log(elementList)

2 scrollByLines与scrollByPages的polyfill

(function polyfillScroll(){
  if(!window.scrollByLines){
    window.scrollByLines=(lines)=>window.scrollBy(0,30*lines);
  }
  if(!window.scrollByPages){
    window.scrollByPages=(pages)=>window.scrollBy(0,document.body.clientHeight*pages);
  }
})()

3 元素宽高属性展示

const getHWInfo = () => ({
    "网页可见区域宽": `${document.body.clientWidth}`,
    "网页可见区域高": `${document.body.clientHeight}`,
    "网页可见区域宽(包括边线和滚动条的宽)": `${document.body.offsetWidth} `,
    "网页可见区域高 (包括边线的宽)": `${document.body.offsetHeight}`,
    "网页正文全文宽": `${document.body.scrollWidth}`,
    "网页正文全文高": `${document.body.scrollHeight}`,
    "网页被卷去的高(ff)": `${document.body.scrollTop}`,
    "网页被卷去的高(ie)": `${document.documentElement.scrollTop}`,
    "网页被卷去的左": `${document.body.scrollLeft}`,
    "网页正文部分上": `${window.screenTop}`,
    "网页正文部分左": `${window.screenLeft}`,
    "屏幕分辨率的高": `${window.screen.height}`,
    "屏幕分辨率的宽": `${window.screen.width}`,
    "屏幕可用工作区高度": `${window.screen.availHeight}`,
    "屏幕可用工作区宽度": `${window.screen.availWidth}`,
    "你的屏幕设置是": `${window.screen.colorDepth}位彩色`,
    "你的屏幕设置": `${window.screen.deviceXDPI} 像素/英寸`,
});
console.table(getHWInfo());

js ios 滑动不流畅_前端_09

4 判断元素是否可以滚动

function eleCanScroll(ele, direction = "y") {
    if (!(ele instanceof HTMLElement)) {
        console.error("eleCanScroll(ele:HTMLElement,direction:'y'|'x'| undefined)");
        return;
    }
    if (direction === "y"){
        if (ele.scrollTop > 0) {
            return true;
        } else {
            ele.scrollTop++;
            const top = ele.scrollTop;
            top && (ele.scrollTop = 0);
            return top > 0;
        } 
    } else if (direction === "x"){
        if (ele.scrollLeft > 0) {
            return true;
        } else {
            ele.scrollLeft++;
            const left = ele.scrollLeft;
            left && (ele.scrollLeft = 0);
            return left > 0;
        } 
    }
}

5 找到某元素外层第一个可滚动的元素

const findScrollEle=(ele,direction="y")=>{
  if(!ele) return null;
  if(eleCanScroll(ele,direction)) return ele;
  return findScrollEle(ele.parentElement,direction);
}