10740

不知道大家有没有遇到过这样的需求,在某个 dom 元素中添加新的子元素,然后要求如果新添加的新元素超出容器的范围,那么我们需要自动滚动到新添加的子元素的位置,如下图所示效果:

那么接下来我们一边学习一些 dom 元素滚动相关的知识点,一边实现一个上图的效果和一些其他滚动相关的功能。

需要了解的dom属性和方法

scrollTop、clientHeight和scrollHeight

scrollTop 属性是一个描述容器元素内容的top值与容器元素( viewport )视口顶部 top 值之间的差值,即容器中内容向上滑动后超出容器视口的部分。可以通过修改此属性控制滚动状态。

clientHeight 是描述容器高度的 dom 属性。

scrollHeight 是描述容器内容高度的 dom 属性。

三个属性的关系如下图所示:

getBoundingClientRect()

此方法用来获取元素布局所需的一些几何属性,比如 left right top bottom height width 等。

srollBy(x,y)

dom 容器的 scrollTo 方法可以用来直接控制滚动条滚动指定的距离。当需要滚动到指定元素时,使用此方法比较方便。

srollTo(x,y)

dom 容器的 scrollTo 方法可以用来直接控制滚动条滚动到指定位置。在控制滚动条滚动到顶部或者底部的时候使用此方法比较方便。

实现滚动控制

我们先准备一个 html

<!DOCTYPE html>
       <title>滚动条设置详解</title>
       <style>
          #scroll_container{
              height: 500px;
              width: 500px;
              overflow-y: scroll;
              padding: 50px;
              box-sizing: border-box;
          .scroll_item{
              height: 200px;
              width: 500px;
              margin-top: 20px;
              background-color: aquamarine;
              display: flex;
              align-items: center;
              justify-content: center;
       </style>
    </head>
       <div  id="scroll_container">
           <div  id="scroll_container">
               <div id="item1" class="scroll_item">
                   <span>1</span>
               </div>
               <div id="item2" class="scroll_item">
                    <span>2</span>
                </div>
                <div id="item3" class="scroll_item">
                    <span>3</span>
                </div>
                <div id="item4" class="scroll_item">
                    <span>4</span>
                </div>
                <div id="item5" class="scroll_item">
                    <span>5</span>
                </div> 
           </div>
           <button onclick="addItem()">添加一个元素</button>
       </div>
    </body>
    <script>
        let container=document.getElementById("scroll_container");
        let index=5;
        //添加一个元素
        function addItem(){
            index+=1;
            let item=`<div id="${'item'+index}" class="scroll_item">
                            <span>${index}</span>
                        </div>`;
            container.innerHTML+=item;  
            setTimeout(()=>{
                scrollToIndex();
    </script>
</html>

上面的代码包含一个可滚动的区域,并可以为滚动区域添加元素,也可以滚动到指定的元素位置,大致效果如下图。

使用scrollTop实现

之前已经说明过scrollTop的含义,我们可以通过修改容器元素scrollTop值来控制滚动条滚动。scrollTop的值越大,滚动条相对于原始状态(scrollTop为0时)的滚动距离越大。

了解了scrollTop的含义,我们就可以利用scrollTop来实现滚动条的控制,那么我们先实现一个滚动到底部的实现,为上面的代码添加一个scrollToBottom()的方法:

function scrollToBottom(){
    let y=container.scrollHeight-container.clientHeight;
    container.scrollTop=y;

对应的如果想要实现滚动到顶部我们只需要设置scrollTop为0即可:

function scrollToTop(){
    container.scrollTop=0;

结合getBoundingClientRect()方法我们也可以轻松实现滚动到指定元素,其中getBoundingClientRect().top表示当前元素元素顶部距离视口顶部的距离:

function scrollToElement(el){
     container.scrollTop+=el.getBoundingClientRect().top;
  • 滚动到底部
    但是上面代码的滚动未免太生硬了,我们可以为它添加一下动画效果,可以借助setInterval()实现一下。分析一下实现动画效果的过程,动画的实现无外乎是把一个变量的变化在一定的时间内完成,因此我们首先需要知道两个变量,变量(scrollTop)偏移量和变化所需时间,而偏移量就是scrollTop的最终值减去原始值,变化时长一般设置成可以修改的参数。了解了以上过程,我们先以滚动到底部为例:
  • //首先编写一个scrollToBottom函数
    function scrollToBottom(el){
                  if(!el){
                      el=container;
                  //原始值
                  let startTop=el.scrollTop;
                  //最终值
                  let endTop=el.scrollHeight-el.clientHeight;
                  //生成一个动画控制函数
                  let scrollAnimationFn=doAnimation(startTop,endTop,300,el);
                  //执行动画,每10ms执行一次
                  let interval=setInterval(()=>{
                    scrollAnimationFn(interval)
                  },10)
     * @description: 一个生成动画控制函数的工厂函数(使用闭包)
     * @param {
        startValue:变量原始值
        endValue:变量最终值
        duration:动画时长
        el:执行滚动动画的元素
     * @return: null
    function doAnimation(startValue,endValue,duration,el){
                  //使用闭包保存变量dy和step(每次动画滚动的距离)
                  let dy=0;
                  let step=(endValue-startValue)/(duration/10);
                  //返回动画控制函数
                  return function(interval){
                      dy+=step;
                      if(dy>=endValue-startValue){
                          clearInterval(interval);
                      el.scrollTop+=step;
    

    修改addItem函数添加滚动到底部动画:

    function addItem(){
                index+=1;
                let item=`<div id="${'item'+index}" class="scroll_item">
                                <span>${index}</span>
                            </div>`;
                container.innerHTML+=item;  
                setTimeout(()=>{
                    // scrollToIndex();
                    scrollToBottom(container);
    

    然后为html加入一个滚动到底部的按钮:

    <button onclick="scrollToBottom()">滚动到底部</button>
    
  • 滚动到顶部
    按照上面的方法也可以实现一个常用的带动画滚动到顶部:
  • //编写一个scrollToTop函数
    function scrollToTop(el){
                  if(!el){
                      el=container;
                  //原始值
                  let startTop=el.scrollTop;
                  //最终值
                  let endTop=0;
                  //生成一个动画控制函数
                  let scrollAnimationFn=doAnimation(startTop,endTop,300,el);
                  //执行动画,每10ms执行一次
                  let interval=setInterval(()=>{
                    scrollAnimationFn(interval)
                  },10)
    

    为了适配滚动到底部我们需要修改一下动画停止的时机判断,修改后的doAnimation()函数如下:

    function doAnimation(startValue,endValue,duration,el){
                  //使用闭包保存变量dy和step(每次动画滚动的距离)
                  let dy=0;
                  let step=(endValue-startValue)/(duration/10);
                  return function(interval){
                      dy+=step;
                      //这里改成使用绝对值判断
                      if(Math.abs(dy)>=Math.abs(endValue-startValue)){
                          clearInterval(interval);
                      el.scrollTop+=step;
    

    最后我们再给html添加一个滚动到底部按钮:

    <button onclick="scrollToTop()">滚动到顶部</button>
    

    实现效果如下图:

  • 滚动到指定元素
    首先为html元素添加所需的按钮和输入框:
  • <input type="number" placeholder="请输入要滚动到的元素index" style="width: 200px;"/>
    <button onclick="scrollToElement()">滚动到指定元素</button>
    

    添加一个滚动指定元素的动画执行函数:

    function scrollToElement(containerEl,el){
                if(!containerEl){
                    //父元素
                    containerEl=container;
                if(!el){
                    //获取到要滚动到的元素
                    let input=document.getElementsByTagName('input')[0];
                    let id='item'+input.value;
                    if(!input.value){
                        id='item'+index;
                    el=document.getElementById(id);
                let startTop=containerEl.scrollTop;
                let endTop=startTop+el.getBoundingClientRect().top
    
    
    
    
        
    ;
                let scrollAnimationFn=doAnimation(startTop,endTop,300,containerEl);
                let interval=setInterval(()=>{
                    scrollAnimationFn(interval)
                },10)
    

    实现效果如下:

    使用scrollTo()实现

    scrollTo(x,y)的使用方法与scrollTop属性的使用方法基本一致,父元素的scrollTo()方法可以控制滚动条滚动到指定位置,实际上相当于设置scrollTop的值。举个例子说明一下:

    //这里以y轴滚动为例
    element.scrollTo(0,y);
    element.scrollTop=y;
    //上面两句的效果相同。
    

    所以,使用scrollTo()方法控制滚动条与使用scrollTop基本一致,我们只需要简单修改doAnimation()函数,代码如下:

    function doAnimation(startValue,endValue,duration,el){
                  //使用闭包保存变量dy和step(每次动画滚动的距离)
                  let dy=0;
                  let step=(endValue-startValue)/(duration/10);
                  return function(interval){
                      dy+=step;
                      if(Math.abs(dy)>=Math.abs(endValue-startValue)){
                          clearInterval(interval);
                      //el.scrollTop+=step;//这行代码修改为如下
                      el.scrollTo(0,el.scrollTop+step);
    

    执行效果与使用scrollTop实现一致。

    使用scrollBy()实现

    我们同样可以使用scrollBy(x,y)实现对滚动条的控制,上面已经说明过,scrollBy()方法是控制滚动条滚动指定距离(注意不是位置)。使用scrollBy()可以很方便的实现滚动到指定元素的需求,代码如下:

    function scrollToElement(containerEl,el){
        //因为getBoundingClientRect().top即为子元素顶部距离父元素顶部的距离,所以这个值就是子元素相对于父元素的偏移量,我们传入这个值到scrollBy中,即滚动到指定元素
        containerEl.scrollBy(0,el.getBoundingClientRect().top);
    

    滚动到底部:

    function scrollToBottom(containerEl){
        let dy=containerEl.scrollHeight-containerEl.clientHeight;
        containerEl.scrollBy(0,dy);
    

    滚动到顶部

    function scrollToTop(containerEl){
        let dy=-(containerEl.scrollHeight-containerEl.clientHeight);
        containerEl.scrollBy(0,dy);
    

    这里我们修改一下动画生成的函数,因为这里我们scrollBy()的参数就是变量的偏移量,所以做出如下修改:

            function scrollToBottom(containerEl){
                  if(!containerEl){
                    containerEl=container;
                  //dy即为偏移量
                  let dy=containerEl.scrollHeight-containerEl.clientHeight;
                  let scrollAnimationFn=doAnimation(dy,300,containerEl);
                  let interval=setInterval(()=>{
                    scrollAnimationFn(interval)
                  },10)
             function scrollToTop(containerEl){
                  if(!containerEl){
                    containerEl=container;
                  //dy即为偏移量
                  let dy=-(containerEl.scrollHeight-containerEl.clientHeight);
                  let scrollAnimationFn=doAnimation(dy,300,containerEl);
                  let interval=setInterval(()=>{
                    scrollAnimationFn(interval)
                  },10)
             function scrollToElement(containerEl,el){
                if(!containerEl){
                    containerEl=container;
                if(!el){
                    let input=document.getElementsByTagName('input')[0];
                    let id='item'+input.value;
                    if(!input.value){
                        id='item'+index;
                    el=document.getElementById(id);
               //dy即为偏移量
                let dy=el.getBoundingClientRect().toplet scrollAnimationFn=doAnimation(dy,300,containerEl);
                let interval=setInterval(()=>{
                    scrollAnimationFn(interval)
                },10)
              * @description: 
              * @param {type} 
              * @return: 
             function doAnimation(dy,duration,el){
                  //使用闭包保存变量exe_dy和step等变量(每次动画滚动的距离)
                  let exe_dy=0;//已经执行的偏移量
                  let step=dy/(duration/10);
                  return function(interval){
                      exe_dy+=step;
                      if(Math.abs(exe_dy)>=Math.abs(dy)){
                          clearInterval(interval);
                      el.scrollBy(0,step);
    

    执行效果与使用scrollTop实现一致。

    以上👆就是自己对dom滚动条控制的详细总结和讲解,以及一些基本使用方法。如果错误还请指正。

    如果觉得可以的话,欢迎点赞o( ̄▽ ̄)d~

  •