实现图片无缝循环滚动

5,089 阅读4分钟

零、需求展示

假设我们需要在一个可视区域中展示合作伙伴的商标,而可视区域不能直接展示整张图片,这时就需要对图片进行滚动播放了。具体情况如下图所示:

image.png

一般情况下,我们可以使外层盒子的scrollLeft++,直至滚动到图片的末端,再设置scrollLeft = 0,使图片回到头部,继续进行滚动,如此反复,达到图片循环滚动的效果。

上述做法的确能达到循环滚动图片的目的,但是直接让图片从尾部跳至头部的做法并不美观。现在我们的需求是:滚动至图片尾部时,头尾无缝衔接,无限循环滚动可以理解成无数张相同的图片排着队滚动的效果。

既然有这种需求,那么就直接开干分析吧。

一、分析一波

上面提到的将无数张图片排队滚动,理论上可以实现需求(但是我并不知道要怎样做到,而且一点也不优雅!),这个方案就不考虑了。

既然不能做到物理上的无数张,那么该怎么模拟出无数张图片的效果呢?

因为图片的宽度是大于屏幕的宽度的,所以理论上,在屏幕中展示的图片数量,是一张或两张(当前一张滚动至尾部,和后一张的头部相接时,屏幕就会显示出这两张图片的各一部分)。

既然如此,那么我们是否可以只使用两张图片,模拟出无数张图片的效果呢?

二、实践一波

在DOM中,其实一直只存在两张一样的图,在这里我称他们为1号图和2号图。而两张图片被放置在一个类名为content-box父级div中。而content-box被放置在宽高确定的盒子(可视区域)中,这里给它的类名为scroll-box。 初始结构如下图,可视区域只能看见一号图片的部分,而溢出部分被hidden了。

<div class="scroll-box">
    <div class="content-box">
      <img src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="">
      <img src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="">
    </div>
</div>

<style>
.scroll-box{
  height: 150px;
  width: 100px;
  overflow: hidden;
 }  
.content-box{
  display: flex;
}
img{
  height: 150px;
  width: 210px;
} 
</style>

image.png

如上图所示,可视区域只能展示1号图片的部分内容,而我们希望将其滚动展示的话,可以使用以下方法实现:

var scrollBox = document.getElementsByClassName('scroll-box')[0]

function scroll(){
  setInterval(() => {
    scrollBox.scrollLeft++
  }, 30)
}

scroll()

screenshots.gif

但现在的问题就是2张图片都滚动展示完之后,就停止滚动了。

为了避免这种情况,达到无限循环的目标,我们需要在1号图片滚动至可视区域之外的时候,将其放置(剪切至)在2号图片的后面。示意图如下所示:

image.png

而如果我们只是简单地将1号图片剪切至2号的后面,那么对于content-box来说,它的宽度其实是没有发生改变的,而在scrollLeft不变的情况下,那么2号图片其实是会被滚动至屏幕以外的。 如果重复这个操作,就会出现content-box宽度不变,scrollLeft变大的情况,最终滚动会停止,无法实现无限滚动的目标。

为了避免上述问题,我们需要在剪切1号图片的同时,为content-box添加padding-left其值等于图片的宽度,这样就可以将盒子撑大,造成content-box中存在3张图片的错觉,这样随着scrollLeft的增大,content-box也能一直滚动。

为了达成上述操作,我们需要使用到的属性和方法如下:

  1. node.clientWidth
  2. node.firstElementChild
  3. node.appendChild()
  4. window.getComputedStyle(node).getPropertyValue(property)

三、完整代码

<div class="scroll-box">
    <div class="content-box">
      <img src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="">
      <img src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="">
    </div>
</div>

<style>
.scroll-box{
  height: 150px;
  width: 100px;
  overflow: hidden;
 }
.content-box{
  display: flex;
}
img{
  height: 150px;
  width: 210px;
}
</style>

<script type="text/javascript">
var contentBox = document.getElementsByClassName('content-box')[0]
var scrollBox = document.getElementsByClassName('scroll-box')[0]
var imgWidth = 210

function scroll(){
  setInterval(() => {
    scrollBox.scrollLeft++
    
    //当一张图片恰好滚动至可视区域外
    if(scrollBox.scrollLeft % 210 === 0){
      var el = contentBox.firstElementChild //获取处于前面的图片
      contentBox.appendChild(el) //appendChild()具备剪切功能,无需手动删除旧节点
      //获取此时content-box的左内边距,值的形式为'999px'
      var paddingLeftStr = window.getComputedStyle(contentBox).getPropertyValue('padding-left') 
      var paddingLeft = parseInt(paddingLeftStr) //转化为数值,如: 999
      contentBox.style.paddingLeft = paddingLeft + 210 + 'px'
    }
    
  }, 30)
}

scroll()
</script>

效果如下图所示,可以在控制台中看见,当1号图片尾部滚至可视区域之外时,content-box的padding-left变大了,撑大了容器,制造有3张图片的效果。而1号和2号图片的位置也发生了改变。如此反复,实现了无缝循环滚动的效果。

screenshots.gif

值得注意的是:MDN上提示:在使用显示比例缩放的系统(如某些手机)上,scrollLeft 可能会是一个小数。需要多加留意这个问题,有必要时对scrollBox.scrollLeft进行parseInt()转换后计算,或其他处理方案。

四、总结一波

  1. 在容器中放置两张一样的图片,并使用scrollLeft++令其滚动。
  2. 当前面的图片尾部滚动至可视区域之外时,增大容器的padding-left撑大盒子。
  3. 使用appendChild()将前面图片剪切至后面图片的后面。
  4. 关机,下班,一天工资到手。