欢迎回到创建完美轮播教程系列。 我们正在使用JavaScript和Popmotion的物理特性,补间和输入跟踪功能制作出易于访问且令人愉快的轮播。
在本教程的第1部分中,我们了解了Amazon和Netflix如何创建轮播,并评估了其方法的利弊。 通过学习,我们决定了轮播的策略,并使用物理方法实现了触摸滚动。
在第2部分中,我们将实现水平鼠标滚动。 我们还将研究一些常见的分页技术并实现它们。 最后,我们将连接一个进度条,该进度条将指示用户通过轮播的距离。
您可以通过打开此CodePen来恢复保存点, 该代码从我们上次中断的地方开始。
水平鼠标滚动
JavaScript轮播很少考虑水平鼠标滚动。 真可惜:在实现基于动量的水平滚动的笔记本电脑和鼠标上,这是迄今为止导航轮播的最快方法。 就像强迫触摸用户通过按钮导航而不是滑动一样糟糕。
幸运的是,它只需几行代码即可实现。 在carousel
功能的末尾,添加一个新的事件侦听器:
container.addEventListener('wheel', onWheel);
在您的startTouchScroll
事件下面,添加一个名为onWheel
的存根函数:
function onWheel(e) {
console.log(e.deltaX)
}
现在,如果在转盘上运行滚轮并检查控制台面板,您将在x轴输出上看到轮距。
与触摸一样,如果轮子的移动大部分是垂直的,则页面应照常滚动。 如果它是水平的,我们要捕获车轮的运动并将其应用于旋转木马。 因此,在onWheel
,将console.log
替换为:
const angle = calc.angle({
x: e.deltaX,
y: e.deltaY
});
if (angleIsVertical(angle)) return;
e.stopPropagation();
e.preventDefault();
如果滚动是水平的,则此代码块将停止页面滚动。 现在,更新滑块的x偏移量只是采用事件的deltaX
属性并将其添加到当前的sliderX
值中即可:
const newX = clampXOffset(
sliderX.get() + - e.deltaX
);
sliderX.set(newX);
我们将重用以前的clampXOffset
函数来包装此计算,并确保轮播不会滚动到其测量边界之外。
节流滚动事件
任何处理输入事件的优秀教程都将说明限制这些事件的重要性。 这是因为滚动,鼠标和触摸事件的触发速度都快于设备的帧频。
您不希望执行不必要的资源密集型工作,例如在一帧中渲染两次轮播,因为这浪费了资源,并且使界面变得迟钝。
本教程没有涉及到这一点,因为Popmotion提供的渲染器实现了Framesync (一个微小的帧同步作业调度程序)。 这意味着您可以连续多次调用(v) => sliderRenderer.set('x', v)
,昂贵的渲染只会在下一帧发生一次。
分页
滚动结束。 现在,我们需要为迄今为止最受欢迎的导航按钮注入一些生命。
现在,本教程是关于交互的,因此可以随意设计这些按钮。 我个人觉得方向箭头更直观(默认情况下完全国际化!)。
分页应如何工作?
在对轮播进行分页时,我们可以采取两种明确的策略: 逐项或第一个模糊的项目 。 只有一种正确的策略,但是,因为我看到了另一种如此频繁地实施的策略,所以我认为值得解释一下它为什么不正确。
1.逐项
只需测量列表中下一项的x偏移量,然后按照该数量设置货架动画即可。 我认为这是一个非常简单的算法,是因为它的简单性而不是用户友好性。
问题在于,大多数屏幕一次都可以显示很多项目,人们会在尝试导航之前先扫描所有项目。
感觉迟钝,即使不是完全令人沮丧。 唯一可行的选择是,如果您知道轮播中的项目的宽度相同或仅比可视区域略小。
但是,如果我们要查看多个项目,则最好使用第一个模糊项目方法:
2.第一个被遮盖的物品
此方法只是在我们要移动轮播的方向上查找第一个被遮挡的项目 ,获取其x偏移量 ,然后滚动到该位置。
这样做时,我们假设用户已经看到了所有当前存在的项目,从而获得了最大数量的新项目。
由于我们要拉入更多商品,因此轮播需要较少的点击才能浏览。 更快的导航将增加参与度,并确保您的用户看到更多您的产品。
事件监听器
首先,让我们设置事件监听器,以便我们可以开始使用分页。
我们首先需要选择上一个和下一个按钮。 在carousel
功能的顶部 ,添加:
const nextButton = container.querySelector('.next');
const prevButton = container.querySelector('.prev');
然后,在carousel
功能的底部 ,添加事件侦听器:
nextButton.addEventListener('click', gotoNext);
prevButton.addEventListener('click', gotoPrev);
最后,在事件侦听器块的上方,添加实际功能:
function goto(delta) {
}
const gotoNext = () => goto(1);
const gotoPrev = () => goto(-1);
goto
是将处理所有分页逻辑的函数。 它只需要一个数字即可代表我们希望分页的行进方向。 gotoNext
和gotoPrev
简单地使用1
或-1
调用此函数。
计算“页面”
用户可以自由滚动该转盘,并且其中有n
项目,并且该转盘可能会调整大小。 因此,传统页面的概念不适用于此处。 我们不会计算页面数。
相反,当调用goto
函数时,我们将朝着delta
的方向看,并找到第一个部分被遮盖的项目。 这将成为我们下一个“页面”上的第一项。
第一步是获取滑块的当前x偏移量,并将其与滑块的整个可见宽度一起使用,以计算要滚动到的“理想”偏移量。 理想的偏移量是如果我们不熟悉滑块的内容将滚动到的位置。 它为我们提供了一个不错的起点,开始搜索我们的第一件商品。
const currentX = sliderX.get();
let targetX = currentX + (- sliderVisibleWidth * delta);
我们可以在这里使用厚脸皮的优化。 通过将我们的targetX
提供给我们在上一个教程中创建的clampXOffset
函数,我们可以查看其输出是否不同于targetX
。 如果是,则意味着我们的targetX
在可滚动范围之外,因此我们无需找出最接近的项目。 我们只是滚动到最后。
const clampedX = clampXOffset(targetX);
targetX = (targetX === clampedX)
? findClosestItemOffset(targetX, delta)
: clampedX;
寻找最近的物品
重要的是要注意,以下代码在您的轮播中所有项目都大小相同的前提下工作。 在此假设下,我们可以进行优化,例如不必测量每个项目的大小。 如果您的项目有不同的尺寸,这仍然将成为一个好起点。
在goto
函数上方,添加最后一个片段中引用的findClosestItemOffset
函数:
function findClosestItem(targetX, delta) {
}
首先,我们需要知道项目的宽度和它们之间的间距。 Element.getBoundingClientRect()
方法可以提供我们需要的所有信息。 对于width
,我们仅测量第一项元素。 要计算项目之间的间距,我们可以测量第一个项目的right
偏移量和第二个项目的left
偏移量,然后从后者减去前者:
const { right, width } = items[0].getBoundingClientRect();
const spacing = items[1].getBoundingClientRect().left - right;
现在,有了传递给函数的targetX
和delta
变量,我们有了快速计算要滚动到的偏移量所需的所有数据。
计算方法是将targetX
的绝对值除以width + spacing
。 这将为我们提供在该距离内可以容纳的确切项目数。
const totalItems = Math.abs(targetX) / (width + spacing);
然后,根据分页方向(我们的delta
)向上或向下取整。 这将给我们提供我们可以容纳的完整物品的数量。
const totalCompleteItems = delta === 1
? Math.floor(totalItems)
: Math.ceil(totalItems);
最后,将该数字乘以width + spacing
即可得出带有完整项目的偏移平齐。
return 0 - totalCompleteItems * (width + spacing);
动画分页
现在我们已经计算出了targetX
,我们可以对其进行动画处理了! 为此,我们将使用网络动画的主力工具tween 。
对于新手来说,“吐温”是短期的BE吐温。 补间在设定的持续时间内从一个值更改为另一个值。 如果您使用过CSS过渡,那是同一回事。
通过补间使用CSS上JavaScript有很多好处(和缺点!)。 在这种情况下,因为我们还将使用物理效果和用户输入来对sliderX
进行动画sliderX
,所以使我们能够更轻松地停留在补间的工作流程中。
这也意味着以后我们可以连接一个进度条,它可以自然地与我们所有的动画一起免费使用。
我们首先要从Popmotion导入tween
:
const { calc, css, easing, physics, pointer, transform, tween, value } = window.popmotion;
在goto
函数的末尾,我们可以添加从currentX
到targetX
动画补间:
tween({
from: currentX,
to: targetX,
onUpdate: sliderX
}).start();
默认情况下,Popmotion将duration
设置为300
毫秒,并ease
为easing.easeOut
。 这些是专门为使响应用户输入的动画具有响应感而选择的,但是可以随意玩转,看看您是否提出了更适合您品牌感觉的东西。
进度指标
对于用户来说,表明他们在轮播中的位置很有用。 为此,我们可以连接一个进度指示器。
您的进度栏可以通过多种方式设置样式。 在本教程中,我们制作了一个彩色的div,高度为5px,在上一个和下一个按钮之间运行。 这是我们将其连接到代码并为栏设置动画的一种方式,这一点很重要,也是本教程的重点。
您尚未看到该指标,因为我们最初使用transform: scaleX(0)
对其进行样式设置。 我们使用scale
变换来调整条形的宽度,因为,正如我们在第1部分中所解释的,变换比更改属性(如left
或在这种情况下, width
更具性能。
它还使我们能够轻松地编写将比例设置为百分比的代码: sliderX
的当前值介于minXOffset
和maxXOffset
之间。
让我们div.progress-bar
在previousButton
选择器之后选择div.progress-bar
:
const progressBar = container.querySelector('.progress-bar');
定义sliderRenderer
,我们可以为progressBar
添加一个渲染器:
const progressBarRenderer = css(progressBar);
现在让我们定义一个函数来更新进度条的scaleX
。
我们将使用称为getProgressFromValue
的calc
函数。 这需要一个范围,在我们的例子中是min
和maxXOffset
,以及第三个数字。 它返回给定范围内第三个数字的进度 ( 0
到1
之间的数字)。
function updateProgressBar(x) {
const progress = calc.getProgressFromValue(maxXOffset, minXOffset, x);
progressBarRenderer.set('scaleX', progress);
}
我们将范围写为maxXOffset, minXOffset
在直观上应该反转)。 这是因为x
是一个负值, maxXOffset
也是一个负值,而minXOffset
是0
。 从技术上讲, 0
是两个数字中较大的一个,但较小的值实际上表示最大偏移量。 负面的吧?
我们希望进度指示器与sliderX
更新,因此让我们更改此行:
const sliderX = value(0, (x) => sliderRenderer.set('x', x));
到这行:
const sliderX = value(0, (x) => {
updateProgressBar(x);
sliderRenderer.set('x', x);
});
现在,每当sliderX
更新时,进度条也会更新。
结论
这就是本分期付款! 您可以在此CodePen上获取最新代码。 我们已经成功引入了水平轮滚动,分页和进度条。
到目前为止,轮播的状态还不错! 在最后一期中,我们将更进一步。 我们将使旋转木马完全键盘可访问,以确保任何人都可以使用它。
当用户尝试使用触摸滚动或分页功能将旋转木马滚动越过其边界时,我们还将使用弹簧动力拖船添加一些令人愉悦的触摸。
回头见!
翻译自: https://code.tutsplus.com/tutorials/create-the-perfect-carousel-part-2--cms-29592