一篇带你学习 CSS 实现卷轴滚动效果

开发 前端
首先 CSS​ 中并没有真正的 3d 滚动,立方体还可以勉强拼接,这种圆形的不行,因此我们需要用其他方式来实现。这里其实是一个最简单的平移动画,只需要将纹理上下无缝平移,结合渐变和阴影,就能得到看似滚动的效果了。

「庆余年2」 终于开播了~最近起点读书APP内上架了庆余年典藏书,最大的特色是里面新加入了全新的阅读皮肤,一个拟物化的卷轴滚动效果,效果如下:

图片

就是在拖动页面时,卷轴会随着页面的滚动而展开或卷起,就像在拖动真的画布一样,非常舒适,录屏可能看着不是很清晰,强烈建议去端内自行体验。

当时看到这个效果时就在思考,如何在 web 中也实现这样一个效果呢?

经过一番琢磨,发现仅使用 CSS 就能完成这样的效果。下面是我复刻的效果。

图片

这是如何实现呢?一起看看吧

一、CSS卷轴滚动的原理

首先 CSS 中并没有真正的 3d 滚动,立方体还可以勉强拼接,这种圆形的不行,因此我们需要用其他方式来实现。

这里其实是一个最简单的平移动画,只需要将纹理上下无缝平移,结合渐变和阴影,就能得到看似滚动的效果了。

先简单布局一下。

<div class="reel">
  <div class="reel-bg"></div>
</div>

这里要用到卷轴的素材图片,是这样的。

两边保留,中间拉伸的一个自适应结构,可以用到border-image,划分出需要拉伸的地方即可。

有关border-image的详细教程可以自行搜索,这里就不展开了,具体实现如下:

.reel{
  position: relative;
  height: 28px;
  margin: 0 15px;
  border-image: url("卷轴.png") fill 42 36/14px 12px/0 12px;
}

这样就能得到卷轴的结构了。

接下来就是纹理了,素材是这个。

我们把这个素材放在卷轴容器了,做上下无限平移动画。

.reel-bg{
  position: absolute;
  left: 0;
  width: 100%;
  height: 368px;
  background: url("纹理.jpg") 50% 0/auto 50%;
	animation: scroll 3s linear forwards infinite;
}
@keyframes scroll {
  0%{
    transform: translateY(-50%);
  }
  100% {
    transform: translateY(-0%);
  }
}

效果如下:

现在看着非常扁平,没有立体感,主要是纹理没有融入到背景之中。

如何将纹理完美的融合到后面的背景呢?没错,需要用到混合模式,这里用正片叠底就行了。

.reel-bg{
  /**/
  mix-blend-mode: multiply;
}

效果就好很多了。

最后给卷轴加点阴影,超出隐藏。

.reel{
  /**/
  overflow: hidden;
  box-shadow: 0 5px 10px 5px rgba(0,0,0,.3), 0 10px 20px 10px rgba(0,0,0,.5);
}

这样就比较真实了。

滚动效果出来了,如何和页面滚动关联起来呢?接着往下看。

二、CSS滚动驱动动画

回到这里,先把整个布局完善一下。

<body>
  <div class="reel">
    <div class="reel-bg"></div>
  </div>
  <article>
    <p>范慎很困难地撑着上眼皮,看着指头算自己这辈子做过些什么有意义的事情,结果右手五根瘦成筷子一样的指头还没有数完,他就叹了一口气,很伤心地放弃了这个工作。病房里的药水味总是这么刺鼻,旁边那床的老爷子前两天已经去地藏王菩萨那里报道了,大概再过几天就轮到自己吧。他得了某种怪病,重症肌无力,就是特别适合言情小说男主角的那种病。据说没得医,将来嗝屁的那天什么都动不了,只有眼泪可以流下来。痛!</p>
    <p>范慎很困难地撑着上眼皮,看着指头算自己这辈子做过些什么有意义的事情,结果右手五根瘦成筷子一样的指头还没有数完,他就叹了一口气,很伤心地放弃了这个工作。病房里的药水味总是这么刺鼻,旁边那床的老爷子前两天已经去地藏王菩萨那里报道了,大概再过几天就轮到自己吧。他得了某种怪病,重症肌无力,就是特别适合言情小说男主角的那种病。据说没得医,将来嗝屁的那天什么都动不了,只有眼泪可以流下来。痛!</p>
    <!--很多文本-->
  </article>
</body>

简单修饰一下,由于卷轴要固定到顶部,可以采用sticky布局。

html{
  background-color: #22312D;
  font-family: cursive;
}
body{
  margin: 0;
}
article{
  background-color: #F5EBD4;
  padding: 1em 0.5em;
  border-left: 10px solid #405C53;
  border-right: 10px solid #405C53;
  margin: 0 15px;
}
p{
  margin: 0;
  padding: 0.2em 0;
  color: #2C402E;
  line-height: 150%;
  text-indent: 2em;
}
h1{
  text-align: center;
  color: #F5EBD4;
}

效果如下:

不过这里还有点问题,由于是整个页面在滚动,内容滚到顶部会漏出来,如下:

所以还需要找个东西遮挡一下,这里我们直接用伪元素实现,设置相同的背景色就行了。

html::before{
  content: '';
  display: block;
  height: 30px;
  background: inherit;
  position: sticky;
  top: 0;
}

由于是sticky,也不用关注层级问题,效果如下:

现在就让卷轴滚动和页面滚动联动起来, 非常简单,只需要添加animation-timeline属性就行了,设置滚动时间线为root,如下:

.reel-bg{
  animation: scroll 1s linear forwards;
  animation-timeline: scroll(root);
}

这样在页面滚动时卷轴也跟着“转动”了。

但看着卷轴转地是不是有点慢了?

确实是这样,这个时候表示页面从头滚到到底部,执行一次动画,也就是滚动一圈,所以页面内容越多,滚动距离越长,那么卷轴转的也就越慢。

下面来修复这个问题

三、优化卷轴滚动速度

简单来处理,可以给个合适的动画次数,比如:

.reel-bg{
  animation: scroll 1s linear forwards 5;
  animation-timeline: scroll(root);
}

这样表示页面从头滚到到底部,会执行5次动画,也就是相当于会滚动5圈,所以看着速度就变快了。

现在就舒服多了。

不过这种处理方式有个问题,动画次数是跟内容长度强相关的,如果在向下滚动时动态加载内容,就需要更新这个值,稍显麻烦。

那么,有没有办法让滚动速度保持均衡呢?也就是无论内容多少,速度都是一致的。

这就需要用到 CSS 动画范围区间了,也就是animation-range,简单来讲就是设置滚动区间。

默认情况下,滚动区间就是从页面顶部滚动到底部,我们可以手动改变这个范围,比如我们设置一个比较大的值。

.reel-bg{
  --s: 999999;
  animation: scroll 1s linear forwards;
  animation-timeline: scroll(root);
  animation-range: 0 calc(var(--s) * 1px);
}

这个表示页面滚动从0滚动到999999px时,上面的卷轴会滚动一圈。这显然不行,我们需要动态去计算滚动的圈数,就是动画播放次数。

.reel-bg{
  animation-iteration-count: calc(var(--s)/184/3.14);
}

为啥是这个值呢?简单解释一下,184是这个卷轴上下的平移距离,然后再除以圆周率3.14,这样看着会更加自然。

这样就能完美实现卷轴滚动了,无论内容长短,滚动速度都是一致的。

图片

核心实现其实就这几行,是不是非常简单。

.reel-bg{
  --s: 999999;
  animation: scroll 1s linear forwards calc(var(--s)/184/3.14);
  animation-timeline: scroll(root);
  animation-range: 0 calc(var(--s) * 1px);
}

四、不支持浏览器兼容

虽然 CSS 实现很简单,但是兼容性还不行,截至目前(2024年4月27日)仅支持Chrome 115+。

所以实际生成中不能直接使用,需要降级处理。

简单的降级是不支持的不执行动画,这个只需要用@supports查询一下就行了。

@supports (animation-timeline: scroll()) {
  .reel-bg{
    --s: 999999;
    animation: scroll 1s linear forwards calc(var(--s)/184/3.14);
    animation-timeline: scroll(root);
    animation-range: 0 calc(var(--s) * 1px);
  }
}

这样在不支持的浏览器,比如 Safari 下就不会自动播放动画了。

如果也想实现卷轴滚动效果,那就需要借助JS的力量,其实实现也不难,就是找到页面的scrollTop和纹理的上下平移关系,做个映射就好了,具体实现如下:

if (!CSS.supports('animation-timeline: scroll()')){
  console.log('不支持animation-timeline')
  const bg = document.querySelector('.reel-bg')
  window.addEventListener('scroll',function(){
    console.log(this.scrollY)
    bg.style.transform = `translateY(${this.scrollY / Math.PI % 184 / 368 * 100 - 80}%)`
  })
}

下面是在Safari中的效果,也能完美支持了。

你也可以访问以下链接查看真实效果。

  • CSS QYN book (juejin.cn)[1]
  • CSS QYN book (codepen.io)[2]

五、总结一下

以上就是本文的全部内容了,一个有趣的交互效果,你学到了吗,下面总结一下本文重点。

  • CSS卷轴滚动的原理其实是一个最简单的平移动画,只需要将纹理上下无缝平移,结合渐变和阴影,就能得到看似滚动的效果了。
  • 简单的平移动画看着非常扁平,没有立体感,主要是纹理没有融入到背景之中。
  • 利用混合模式正片叠底,可以让平移动画看着像滚动动画了。
  • CSS 滚动驱动动画可以让页面滚动和卷轴滚动联动起来。
  • 默认情况下,页面从头滚到到底部,执行一次动画,也就是滚动一圈,所以页面内容越多,滚动距离越长,卷轴转的也就越慢。
  • 可以通过改变动画重复次数来调整卷轴滚动速度,局限是需要根据页面滚动长度来计算合适的数值。
  • 用足够大的 CSS 动画范围区间(animation-range)配合动画重复次数可以实现卷轴滚动速度保持均衡。
  • 目前兼容性还有些差,可以用CSS.supports做兼容处理。

[1]CSS QYN book (juejin.cn): https://code.juejin.cn/pen/7362400098958966793。

[2]CSS QYN book (codepen.io): https://codepen.io/xboxyan/pen/VwNqEoE。

责任编辑:姜华 来源: 前端侦探
相关推荐

2022-03-17 19:29:04

CSS切角自适应

2021-08-23 06:25:57

CSS 技巧animation

2021-06-16 08:28:25

unary 方法函数技术

2022-12-14 08:03:27

CSS变量前端

2022-05-05 07:40:07

maskCSS

2023-04-21 08:11:54

KubernetesPod

2021-03-26 09:57:51

SVGHtml基础SVG图像

2021-04-07 06:11:37

Css前端CSS定位知识

2021-01-25 05:39:54

Css前端Border

2021-05-31 09:30:36

Css前端CSS 特效

2020-11-03 19:18:28

CSS对齐文本

2022-04-08 08:32:40

mobx状态管理库redux

2020-11-17 11:10:21

CSS选择器HTML

2023-04-20 08:00:00

ES搜索引擎MySQL

2021-05-20 06:57:16

RabbitMQ开源消息

2021-07-13 11:37:47

cpu架构Linux

2022-04-21 08:41:49

css前端

2021-05-20 09:02:59

CSS单位长度

2023-06-28 15:04:59

CSSHTML

2023-08-01 14:34:12

HTMLCSS
点赞
收藏

51CTO技术栈公众号