头图

🔥超好用!Chrome新增滚动快照事件,解锁滚动新玩法🚀

JavaScript滚动快照事件:触发滚动动画

原文链接:https://dev.to/logrocket/javascript-scroll-snap-events-for-scroll-triggered-animations-55b2
作者:Abiola Farounbi
译者:倔强青铜三

前言

大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!!!

从Chrome 129及以上版本开始,用户可以访问新的滚动快照事件——scrollsnapchangescrollsnapchanging。这些新事件为用户提供了对CSS滚动快照功能的独特且动态的控制。

scrollsnapchanging事件

当浏览器在滚动手势期间识别出一个新的滚动快照目标将在滚动结束时被选中时,会触发此事件。它也被称为待定滚动快照目标。

当用户在页面上缓慢滚动,手指不离开屏幕,经过多个潜在的快照目标时,scrollsnapchanging事件会持续触发。然而,如果用户快速滑动页面,一次滚动手势就经过多个快照目标,该事件则不会触发。相反,事件仅针对最终可能固定下来的快照目标触发。

scrollsnapchange事件

只有当滚动手势导致新的滚动快照目标被固定下来时,才会触发此事件。它发生在滚动停止后立即,scrollend事件触发前。

这个事件的另一个独特行为是,在进行中的滚动手势期间不会触发,这意味着滚动尚未结束,快照目标也可能尚未改变。

如何实现滚动快照事件?

滚动快照事件与CSS滚动快照属性协同工作。这些事件通常被分配给包含滚动快照目标的父容器。

这两个JavaScript滚动快照事件共享SnapEvent对象,该对象包含两个在这些事件中使用的重要属性:

  • snapTargetBlock — 当事件被触发时,提供在块方向上被快照的元素的引用。如果仅在内联方向上发生快照,则返回null,因为在块方向上没有元素被快照
  • snapTargetInline — 当事件被触发时,提供在内联方向上被快照的元素的引用。如果仅在块方向上发生快照,则返回null,因为在内联方向上没有元素被快照

通过将这些属性与滚动快照事件和事件处理函数一起使用,您可以轻松地识别被快照的元素,并根据需要进行自定义或应用预定义的样式。在本文中,我们将探讨如何实现这一点。

让我们来看一个快速示例,展示如何使用这些事件:

这个示例演示了一个应用了垂直滚动快照的滚动容器,其中一组网格框作为滚动快照目标。这些框由div元素表示。

最初,所有框的背景都是白色的。在滚动操作期间,当新的快照目标待定时,背景颜色变为绿色。一旦快照目标被选中,背景平滑过渡到红色,文字为白色。

以下是事件的实现方式。代码中的注释提供了逐步的解释:

对于scrollsnapchanging

scrollContainer.addEventListener("scrollsnapchanging", (event) => {
  // 为滚动容器添加"scrollsnapchanging"事件的事件监听器
  // 此事件在滚动期间触发,预测潜在的快照目标
  const previousPending = document.querySelector(".snap-incoming");
  // 查找具有"snap-incoming"类的现有元素

  if (previousPending) {
    previousPending.classList.remove("snap-incoming");
    // 如果存在这样的元素,则移除"snap-incoming"类
    // 这确保了只有一个元素具有此活动状态
  }
  event.snapTargetBlock.classList.add("snap-incoming");
  // 为新的快照目标添加"snap-incoming"类

  updateEventLog("Snap Changing");
  // 更新事件日志,显示snapchanging事件已发生
});

对于scrollsnapchange

scrollContainer.addEventListener("scrollsnapchange", (event) => {
  // 为滚动容器添加"scrollsnapchange"事件的事件监听器
  // 当滚动快照完成且新的目标被选中时触发此事件

  const currentlySnapped = document.querySelector(".snap-active");
  // 查找具有"snap-active"类的现有元素

  if (currentlySnapped) {
    currentlySnapped.classList.remove("snap-active");
    // 如果存在这样的元素,则移除"snap-active"类
  }

  event.snapTargetBlock.classList.add("snap-active");
  // 为新的快照目标添加"snap-active"类

  updateEventLog("Snap Changed");
  // 更新事件日志,显示snapchange事件已发生
});

探索独特的用例和演示

这些事件为滚动触发的动画提供了独特的用例,因为它们允许在活跃滚动(进行中)期间以及到达快照目标时对动画进行精确控制。让我们来看一些这些事件的独特用例。

精确快照控制的轮播图

这个用例展示了使用CSS滚动快照属性实现的水平滚动轮播图,并额外添加了一个功能,即当每个幻灯片成为当前快照目标时,动画其内容。

通过使用scrollsnapchange事件,动态地将snap-active样式类直接应用于当前轮播图幻灯片,从而平滑地为轮播图添加动画,创建更好的交互式演示。

以下是scrollsnapchange事件的使用步骤:

// 选择轮播图容器
const carousel = document.getElementById("carousel");
// 获取轮播图中的所有幻灯片
const slides = carousel.querySelectorAll(".carousel-slide");

// 为滚动容器添加"scrollsnapchange"事件的事件监听器
carousel.addEventListener("scrollsnapchange", (event) => {
  // 获取水平滚动就位的目标幻灯片
  const snapTarget = event.snapTargetInline;

  // 这部分更新当前幻灯片以供导航按钮使用
  const slideWidth = carousel.clientWidth;
  currentSlide = Math.round(carousel.scrollLeft / slideWidth);

  // 从先前活动的幻灯片移除'snap-active'类
  const currentlySnapping = document.querySelector(".snap-active");
  if (currentlySnapping) {
    currentlySnapping.classList.remove("snap-active");
  }
  // 为新快照幻灯片添加'snap-active'类以触发动画
  snapTarget.classList.add("snap-active");
});

在CSS中,snap-active类与元素的现有类名结合。当scrollsnapchange事件触发并将此类添加到元素的类列表中时,相应的CSS规则立即应用,动态更新幻灯片的外观并实时动画其内容:

.carousel-slide.snap-active {
  opacity: 1;
  scale: 1;
}
.carousel-slide.snap-active .content {
  opacity: 1;
  transform: translateY(0);
}

快照时自动播放视频预告片

这个用例的功能类似于用户与Instagram Reels、YouTube Shorts和TikTok视频的交互方式。它由一系列短视频组成,当快照到视图中时自动播放,过渡到另一个视频时暂停。

通过同时使用scrollsnapchangingscrollsnapchange事件,精确控制视频播放,确保只有当前可见的视频在播放,而其他视频保持暂停。

以下是事件的使用方式:

const snapContainer = document.querySelector(".snap-container");
const trailers = document.querySelectorAll("video");

snapContainer.addEventListener("scrollsnapchange", (event) => {
  // 获取当前快照项的视频元素
  const visibleTrailer = event.snapTargetBlock.children[0];
  if (visibleTrailer) {
    // 当视频快照到视图中时播放
    visibleTrailer.play();
  }
});

snapContainer.addEventListener("scrollsnapchanging", (event) => {
  const visibleTrailer = event.snapTargetBlock.children[0];
  if (visibleTrailer) {
    // 在过渡到另一个快照时暂停当前可见的视频
    visibleTrailer.pause();
  }
});

在这个用例中,视频元素不是直接嵌套在snapTargetBlock内的。因此,需要访问snapTargetBlockchildren来获取视频元素:

const visibleTrailer = event.snapTargetBlock.children[0];

动态滚动叙事

这个用例突出了一个动态且交互式的页面,展示了不同的“快照动画”。这些动画在部分快照到视图中时触发,当部分滚动出视图时,其相应的动画平滑移除。这种方法创建了一个独特且引人入胜的滚动叙事体验。

在下面的演示中,我们有一个被分为四个不同部分的着陆页,每个部分都有独特的动画,随着滚动而逐渐显示。这是通过使用scrollsnapchangingscrollsnapchange事件实现的,这些事件根据滚动行为动态添加或移除动画。

逻辑的工作方式如下:

const scrollContainer = document.querySelector(".container");

// 处理scrollsnapchange事件
scrollContainer.addEventListener("scrollsnapchange", (event) => {
  // 获取当前快照目标块
  const snapTarget = event.snapTargetBlock;

  // 为第一个子元素(如果存在)添加"active"类
  if (snapTarget.children[0]) {
    snapTarget.children[0].classList.add("active");
  }

  // 为第二个子元素(如果存在)添加"active"类
  if (snapTarget.children[1]) {
    snapTarget.children[1].classList.add("active");
  }
});

scrollContainer.addEventListener("scrollsnapchanging", (event) => {
  // 获取当前快照目标块
  const snapTarget = event.snapTargetBlock;

  // 从第一个子元素(如果存在)移除"active"类
  if (snapTarget.children[0]) {
    snapTarget.children[0].classList.remove("active");
  }

  // 从第二个子元素(如果存在)移除"active"类
  if (snapTarget.children[1]) {
    snapTarget.children[1].classList.remove("active");
  }
});

与之前的实现类似,我们需要访问所有子元素,以便分别对它们应用不同的动画。

现在,让我们看看作品集部分的CSS:

@keyframes slideDown {
  0% {
    opacity: 0;
    transform: translateY(-100%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideUp {
  0% {
    opacity: 0;
    transform: translateY(100%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

.portfolio-content.active {
  animation: slideDown 1s ease-out forwards;
}

.portfolio-grid.active img {
  animation: slideUp 1s ease-out forwards;
}

.portfolio-grid.active img:nth-child(1) {
  animation-delay: 0.2s;
}
.portfolio-grid.active img:nth-child(2) {
  animation-delay: 0.4s;
}
.portfolio-grid.active img:nth-child(3) {
  animation-delay: 0.6s;
}

我们创建了两种不同的动画:slideupslidedown。这些动画被添加到active类以及相应元素的类名中。

为了定位各个子元素,我们使用了:nth-child()伪类。这种方法允许我们创建平滑的动画流程。

解决CSS与JS驱动快照之间的差距

早在2022年,滚动快照就完全依赖CSS来定义快照点和样式快照目标。虽然CSS通过scroll-snap-typescroll-snap-align等属性提供了一种干净且声明式的方式来启用快照,但它缺乏对快照行为和样式的动态控制。

在更复杂的情况下,例如实现轮播图或画廊时,通常需要JavaScript来管理状态、跟踪交互并应用自定义样式。

JavaScript滚动快照事件的引入填补了这一空白。这些事件提供了对快照目标的实时预测和动态交互,能够添加CSS单独无法实现的自定义行为和动画。

例如,您可以使用scroll-snap-type来控制快照行为,scroll-snap-align来确定SnapEvent对象返回的相关属性:

  • block轴 — snapTargetBlock引用快照元素
  • inline轴 — snapTargetInline引用快照元素
  • both轴 — 两个属性都返回相应的快照元素

通过结合CSS滚动快照属性和JavaScript滚动事件,您可以创建增强的滚动体验,同时保持平滑、直观的交互,如上例所示。

浏览器兼容性

现代浏览器开始支持这些新事件,但目前它们仅限于Chrome 129及以上版本和Edge。

浏览器兼容性

与Intersection Observer API的比较

在这些JavaScript滚动事件引入之前,Intersection Observer API被用来跟踪元素穿过滚动端口,并根据元素在视口中的填充程度识别当前快照目标。

然而,这种方法有限,没有关于快照目标何时以及如何改变的实时更新,使其在更复杂的滚动驱动界面中效果不佳。

以下是滚动快照事件和Intersection Observer API的简要比较,概述了它们的关键差异和优势:

特性滚动快照事件Intersection Observer API
主要用途通过跟踪滚动交互期间目标的变化来识别精确的滚动快照点用于检测元素何时进入、离开或与指定视口容器相交
实现难度对于滚动特定交互更容易实现需要更多配置和努力来设置,但提供了超出滚动快照的更广泛功能
支持的事件scrollsnapchanging scrollsnapchange在可见性变化时触发自定义回调
样式快照目标直接识别并允许样式快照目标需要自定义逻辑来确定快照目标
跨浏览器支持有限;目前仅支持Chrome 129+和Edge(基于Chromium)。尚未在Safari或Firefox中提供在现代浏览器中有广泛支持
最后感谢阅读!欢迎关注我,微信公众号:倔强青铜三。欢迎点赞收藏关注,一键三连!!!

倔强青铜三
28 声望0 粉丝