在本教程中,您将学习如何使用原生 javascript 和 css 实现滚动动画。使用自定义实现(相对于库)的主要优点是它允许我们优化我们的功能以提高可访问性和性能。
滚动动画演示
这是滚动动画元素如何工作的示例:
我们的实现依赖于动画的 CSS 和 JavaScript 来处理触发必要的样式。我们将从创建布局开始。
1.定义页面结构
我们将使用 html 创建页面布局,然后为要在滚动时设置动画的元素分配一个通用类名。这个类名是我们将在 javaScript 中定位的。
在上面的演示中,元素被分配了类名js-scroll
,因此 HTML 看起来像这样:
<header> <!--this is where the content of the header goes--> </header> <section class="scroll-container"> <div class="scroll-element js-scroll"> </div> <div class="scroll-caption"> This animation fades in from the top. </div> </section>
2.使用 CSS 进行样式设置
CSS 做了很多繁重的工作,因为它决定了每个元素的动画样式。在这种情况下,我们将使用类 name 为元素设置动画scrolled
。
这是一个简单的淡入动画示例:
.js-scroll { opacity: 0; transition: opacity 500ms; } .js-scroll.scrolled { opacity: 1; }
使用此代码,js-scroll
页面上的任何元素都被隐藏,不透明度为 ,0
直到scrolled
应用了类名。
3.使用 JavaScript 定位元素
一旦我们有了布局和样式,我们将创建 JavaScript 函数,以便在元素滚动到视图时为其分配类名。我们还将淡出 JavaScript 而不是 CSS 中的元素,因为我们希望元素在浏览器未启用 JavaScript 的情况下可见。
我们将这样分解逻辑:
获取
js-scroll
页面上的所有元素淡出元素
检测元素何时在视口内
scrolled
如果元素在视图中,则将类名分配给元素。
页面上的目标元素
我们将js-scroll
使用该document.queryselectorAll()
方法定位页面上的所有元素。它应该如下所示:
const scrollElements = document.querySelectorAll(".js-scroll");
淡出元素
首先,我们需要删除 CSS 中的opacity:0
for .js-scroll
。然后我们在 JavaScript 中包含这一行:
scrollElements.forEach((el) => { el.style.opacity = 0 })
如果在浏览器中禁用 JavaScript,这允许元素具有其默认样式。
检测元素何时在视图中
我们可以通过确定元素与页面顶部的距离是否小于页面可见部分的高度来检测元素何时在用户的视野中。
在 JavaScript 中,我们使用getBoundingClientRect().top
方法来获取元素到页面顶部的距离,window.innerHeight
或者document.documentElement.clientHeight
获取视口的高度。
来源:Element.getBoundingClientRect() - Web api | MDN
我们将elementInView
使用上述逻辑创建一个函数:
const elementInView = (el) => { const elementTop = el.getBoundingClientRect().top; return ( elementTop <= (window.innerHeight || document.documentElement.clientHeight) ); };
我们可以修改这个函数来检测元素何时将x
像素滚动到页面中,或者检测页面的百分比何时滚动。
const elementInView = (el, scrollOffset = 0) => { const elementTop = el.getBoundingClientRect().top; return ( elementTop <= ((window.innerHeight || document.documentElement.clientHeight) - scrollOffset) ); };
true
在这种情况下,如果元素滚动scrollOffset
到页面中的量,则函数返回。修改逻辑为我们提供了基于百分比滚动定位元素的不同功能。
const elementInView = (el, percentageScroll = 100) => { const elementTop = el.getBoundingClientRect().top; return ( elementTop <= ((window.innerHeight || document.documentElement.clientHeight) * (percentageScroll/100)) ); };
自定义实现的另一个好处是我们可以定义逻辑以满足我们的特定需求。
注意:可以使用Intersection Observer API来实现相同的效果,但是,在撰写本文时,Internet Explorer 不支持 Intersection Observer,因此它不符合我们“适用于所有浏览器”的要求。
为元素分配类名
现在我们能够检测我们的元素是否已经滚动到页面中,我们需要定义一个函数来处理元素的显示——在这种情况下,我们通过分配scrolled
类名来显示元素。
const displayScrollElement = (element) => { element.classList.add("scrolled"); };
然后,我们将我们的逻辑与显示函数结合起来,并使用该方法在所有元素forEach
上调用该函数。js-scroll
const handleScrollAnimation = () => { scrollElements.forEach((el) => { if (elementInView(el, 100)) { displayScrollElement(el); } }) }
一个可选功能是在元素不再可见时将其重置为其默认状态。我们可以通过定义一个hideScrollElement
函数并将其包含在else
上述函数的语句中来做到这一点:
const hideScrollElement = (element) => { element.classList.remove("scrolled"); }; const handleScrollAnimation = () => { scrollElements.forEach((el) => { if (elementInView(el, 100)) { displayScrollElement(el); } else { hideScrollElement(el); } }) }
最后,我们将上述方法传递给窗口上的滚动事件***器,以便在用户滚动时运行。
window.addeventListener('scroll', () => { handleScrollAnimation(); })
还有viola,我们已经实现了滚动动画所需的所有功能。
我们可以在这个演示中看到逻辑是如何工作的:
完整的代码如下所示。JavaScript:
const scrollOffset = 100; const scrollElement = document.querySelector(".js-scroll"); const elementInView = (el, offset = 0) => { const elementTop = el.getBoundingClientRect().top; return ( elementTop <= ((window.innerHeight || document.documentElement.clientHeight) - offset) ); }; const displayScrollElement = () => { scrollElement.classList.add('scrolled'); } const hideScrollElement = () => { scrollElement.classList.remove('scrolled'); } const handleScrollAnimation = () => { if (elementInView(scrollElement, scrollOffset)) { displayScrollElement(); } else { hideScrollElement(); } } window.addEventListener('scroll', () => { handleScrollAnimation(); })
CSS:
.js-scroll { width: 50%; height: 300px; background-color: #DADADA; transition: background-color 500ms; } .js-scroll.scrolled { background-color: aquamarine; }
4.更多的 CSS 动画
我们再来看看第一个demo:
我们看到元素以不同的动画出现。这是通过为类名分配不同的 CSS 动画来完成的。此演示的 HTML 如下所示:
<section class="scroll-container"> <div class="scroll-element js-scroll fade-in"> </div> <div class="scroll-caption"> This animation fades in. </div> </section> <section class="scroll-container"> <div class="scroll-element js-scroll fade-in-bottom"> </div> <div class="scroll-caption"> This animation slides in to the top. </div> </section> <section class="scroll-container"> <div class="scroll-element js-scroll slide-left"> </div> <div class="scroll-caption"> This animation slides in from the left. </div> </section> <section class="scroll-container"> <div class="scroll-element js-scroll slide-right"> </div> <div class="scroll-caption"> This animation slides in from the right. </div> </section>
注意:类旁边的js-scroll
类是我们在 CSS 中处理不同动画的目标。在我们的 CSS 样式表中,我们将拥有:
.scrolled.fade-in { animation: fade-in 1s ease-in-out both; } .scrolled.fade-in-bottom { animation: fade-in-bottom 1s ease-in-out both; } .scrolled.slide-left { animation: slide-in-left 1s ease-in-out both; } .scrolled.slide-right { animation: slide-in-right 1s ease-in-out both; } @keyframes slide-in-left { 0% { transform: translateX(-100px); opacity: 0; } 100% { transform: translateX(0); opacity: 1; } } @keyframes slide-in-right { 0% { transform: translateX(100px); opacity: 0; } 100% { transform: translateX(0); opacity: 1; } } @keyframes fade-in-bottom { 0% { transform: translateY(50px); opacity: 0; } 100% { transform: translateY(0); opacity: 1; } } @keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
我们不需要对 JavaScript 代码进行任何更改,因为逻辑保持不变。这意味着我们可以在页面上拥有任意数量的不同动画,而无需编写新函数。
5.通过油门提高性能
每当我们在滚动监听器中包含一个函数时,每次用户滚动页面时都会调用该函数。滚动 500px 的页面会导致函数至少被调用 50 次。如果我们试图在页面上包含很多元素,这可能会导致我们的页面显着变慢。
节流功能救援
我们可以通过使用“节流函数”来减少函数被调用的次数。节流函数是一个高阶函数,它在指定的时间间隔内只调用一次传递给它的函数。
它对滚动事件特别有用,因为我们不需要检测用户滚动的每个像素。例如,如果我们有一个定时器为 100 毫秒的油门函数,那么用户每滚动 100 毫秒,该函数只会被调用一次。
可以像这样在 JavaScript 中实现一个节流函数:
//initialize throttleTimer as false let throttleTimer = false; const throttle = (callback, time) => { //don't run the function while throttle timer is true if (throttleTimer) return; //first set throttle timer to true so the function doesn't run throttleTimer = true; setTimeout(() => { //call the callback function in the setTimeout and set the throttle timer to false after the indicated time has passed callback(); throttleTimer = false; }, time); }
我们可以在滚动事件监听器上修改我们的窗口,使其看起来像这样
window.addEventListener('scroll', () => { throttle(handleScrollAnimation, 250); })
现在我们的handleScrollAnimation
函数在用户滚动时每 250 毫秒调用一次。
更新后的演示如下所示:
6.提高可访问性
实现自定义功能时,性能并不是唯一的要求;我们还需要设计可访问性。无障碍设计意味着考虑用户的选择和情况。有些用户可能根本不想拥有动画,所以我们需要考虑到这一点。
减少运动媒体查询
我们可以使用prefers-reduced-motion 查询和 JavaScript 实现来做到这一点。
“prefers-reduced-motion [...] 用于检测用户是否要求系统将其使用的非必要运动量降至最低” – MDN
修改我们上面的代码,查询在 CSS 中看起来像这样:
@media (prefers-reduced-motion) { .js-scroll { opacity: 1; } .scrolled { animation: none !important; } }
通过这些代码行,我们确保动画元素始终可见,并且所有元素的动画都关闭。
并非所有浏览器都完全支持 prefers-reduced-motion 查询,因此我们可以包含 JavaScript 后备:
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); window.addEventListener("scroll", () => { //check if mediaQuery exists and if the value for mediaQuery does not match 'reduce', return the scrollAnimation. if (mediaQuery && !mediaQuery.matches) { handleScrollAnimation() } });
This way, if the user prefers reduced motion, the handleScrollAnimation
function is never called at all.
这样,如果用户更喜欢减少运动,handleScrollAnimation
则根本不会调用该函数。
这就是如何使用 JavaScript 在 Scroll 上制作动画
我们现在有一个高性能、完全可访问的“滚动动画”功能实现,它适用于所有浏览器!
- 页面上的目标元素
- 淡出元素
- 检测元素何时在视图中
- 为元素分配类名
- 节流功能救援
- 减少运动媒体查询