在 Popmotion 介绍系列的第一部分,我们学习了如何使用 基于时间的动画,例如
tween
和 。我们还学习了如何在dom上使用这些动画,使用 performant ,keyframes
styler。
在第二部分,我们学习了如何使用 pointer
tracking 和 record velocity
。然后我们用它来驱动 基于速度的动画 spring
、 decay
和 physics
。
在这最后一部分中,我们将创建一个scrubber 小部件,我们将使用它来擦洗 keyframes
动画。我们将结合指针跟踪来制作小部件本身, spring
并 decay
使其比普通的洗涤器更具内在感觉。
自己试试吧:
入门
标记
首先,为 html 模板 fork这个 CodePen。和以前一样,因为这是一个中级教程,所以我不会介绍所有内容。
值得注意的是,洗涤器上的手柄由两个 div
元素组成:.handle
和 .handle-hit-area
。
.handle
是洗涤器手柄位置的圆形蓝色视觉指示器。我们将它包裹在一个不可见的点击区域元素中,以使触摸屏用户更容易抓取该元素。
导入函数
在 JS 面板的顶部,导入我们将在本教程中使用的所有内容:
const { easing, keyframes, pointer, decay, spring, styler, transform, listen, value } = popmotion; const { pipe, clamp, conditional, linearSpring, interpolate } = transform;
选择元素
在本教程中,我们将需要三个元素。我们将为 制作动画 .box
,拖动并制作动画 .handle-hit-area
,并测量 .range
。
styler
让我们也为我们要制作动画的元素创建 s:
const box = document.queryselector('.box'); const boxStyler = styler(box); const handle = document.querySelector('.handle-hit-area'); const handleStyler = styler(handle); const range = document.querySelector('.range');
关键帧动画
对于我们的可擦洗动画,我们将 .box
使用 keyframes
. 但是,我们可以 使用本教程后面概述的相同方法轻松地擦除动画tween
。 timeline
const boxAnimation = keyframes({ values: [0, -150, 150, 0], easings: [easing.backOut, easing.backOut, easing.easeOut], duration: 2000 }).start(boxStyler.set('x'));
您的动画现在将开始播放。但我们不希望这样!让我们暂时暂停一下:
boxAnimation.pause();
拖动x-axis
是时候用来 pointer
拖动我们的洗涤器手柄了。在之前的教程中,我们同时使用了 x
和 y
属性,但是使用scrubber,我们只需要 x
.
我们更喜欢让我们的代码保持可重用性,并且跟踪单个 pointer
轴是一个很常见的用例。因此,让我们想象一下,创建一个名为 pointerX
.
它的工作原理与它完全一样 pointer
,只是它只接受一个数字作为参数并只输出一个数字 ( x
):
const pointerX = (x) => pointer({ x }).pipe(xy => xy.x);
在这里,您可以看到我们使用了 pointer
被调用 的方法pipe
。 pipe
到目前为止我们看到的所有 Popmotion 动作都可以使用,包括keyframes
.
pipe
接受多种功能。当操作被 start
ed 时,所有输出将依次通过这些函数中的每一个,在 update
提供给 start
触发的函数之前。
在这种情况下,我们的功能很简单:
xy => xy.x
它所做的只是获取 { x, y }
通常输出的对象 pointer
并仅返回 x
轴。
事件监听器
pointerX
在我们开始使用我们的新功能进行跟踪之前,我们需要知道用户是否已经开始按下手柄 。
在上一个教程中,我们使用了传统 addeventListener
函数。这一次,我们将使用另一个名为 的 Popmotion 函数 listen
。 listen
还提供了一个 pipe
方法,以及对所有操作方法的访问,但我们不打算在这里使用它。
listen
允许我们使用单个函数将事件***器添加到多个事件,类似于jquery。所以我们可以把前面的四个事件监听器浓缩成两个:
listen(handle, 'mousedown touchstart').start(startDrag); listen(document, 'mouseup touchend').start(stopDrag);
移动手柄
稍后我们将需要句柄的 x velocity
,所以让我们将它设为 a value
,正如我们在上一个教程中学到的那样,我们可以查询速度。在我们定义之后的那一行 handleStyler
,添加:
const handleX = value(0, handleStyler.set('x'));
现在我们可以添加我们的 startDrag
和stopDrag
函数:
const startDrag = () => pointerX(handleX.get()) .start(handleX); const stopDrag = () => handleX.stop();
现在,可以将手柄擦洗到滑块边界之外,但我们稍后会回到这一点。
擦洗
现在我们有了一个视觉功能的擦洗器,但我们并没有擦洗实际的动画。
每个 value
人都有 subscribe
方法。这允许我们附加多个订阅者以在value
更改时触发。我们想 在更新keyframes
时寻找动画 。handleX
首先,测量滑块。在我们定义之后的那一行 range
,添加:
const rangeWidth = range.getBoundingClientRect().width;
keyframes.seek
接受从 0
to 表示的进度值1
,而 our handleX
设置为从0
to的像素值rangeWidth
。
我们可以 通过将当前像素测量值除以 来将像素测量值转换0
为 范围。在之后的行上 ,添加此订阅方法:1
rangeWidth
boxAnimation.pause()
handleX.subscribe(v => boxAnimation.seek(v / rangeWidth));
现在,如果您使用擦洗器,动画将成功擦洗!
额外一英里
弹簧边界
仍然可以将洗涤器拉到整个范围的边界之外。为了解决这个问题,我们可以简单地使用一个 clamp
函数来确保我们不会输出 0, rangeWidth
.
相反,我们将采取额外的步骤并将弹簧连接到滑块的末端。当用户将手柄拉到允许范围之外时,它会向后拉。如果用户在超出范围时释放手柄,我们可以使用 spring
动画将其重新拉回。
我们将使这个过程成为我们可以提供给该pointerX
pipe
方法的单个函数。通过创建一个单一的、可重用的函数,我们可以在任何 Popmotion 动画中重用这段代码,并具有可配置的范围和弹簧强度。
首先,让我们在最左边的极限处应用一个弹簧。我们将使用两个变压器, conditional
和 linearSpring
。
const springRange = (min, max, strength) => conditional( v => v < min, linearSpring(strength, min) );
conditional
有两个函数,一个断言和一个转换器。断言接收提供的值并返回 true
or false
。如果它返回 true
,将向第二个函数提供要转换和返回的值。
在这种情况下,断言是说:“如果提供的值小于 min
,则将此值传递给 linearSpring
转换器。” 是一个简单的 linearSpring
弹簧函数,与 physics
或 spring
动画不同,它没有时间概念。提供它 a strength
和 a target
,它将创建一个函数,将任何给定值“吸引”到具有定义强度的目标。
用这个替换我们的 startDrag
函数:
const startDrag = () => pointerX(handleX.get()) .pipe(springRange(0, rangeWidth, 0.1)) .start(handleX);
我们现在 x
通过我们的函数传递指针的偏移量 springRange
,所以如果您将手柄拖过最左侧,您会注意到它向后拉。
将其应用于最右侧是 conditional
使用独立 pipe
函数将第二个与第一个组合起来的问题:
const springRange = (min, max, strength) => pipe( conditional( v => v < min, linearSpring(strength, min) ), conditional( v => v > max, linearSpring(strength, max) ) );
编写类似函数的另一个好处 springRange
是它变得非常可测试。它返回的函数和所有的转换器一样,是一个接受单个值的纯函数。您可以测试此函数以查看它是否通过位于内部 min
且未 max
更改的值,以及是否将弹簧应用于位于外部的值。
如果您在手柄位于范围之外时松开手柄,它现在应该弹回范围内。为此,我们需要调整 stopDrag
函数以触发 spring
动画:
const stopDrag = () => { const x = handleX.get(); (x < 0 || x > rangeWidth) ? snapHandleToEnd(x) : handleX.stop(); };
我们的 snapHandleToEnd
函数如下所示:
const snapHandleToEnd = (x) => spring({ from: x, velocity: handleX.getVelocity(), to: x < 0 ? 0 : rangeWidth, damping: 30, stiffness: 5000 }).start(handleX);
您可以看到它 to
被设置为 0
或 rangeWidth
取决于手柄当前位于滑块的哪一侧。通过玩 damping
和 stiffness
,您可以玩各种不同的春天感觉。
动量滚动
我一直很欣赏的对ios洗涤器的一个很好的触摸是,如果你扔掉手柄,它会逐渐减速而不是完全停止。我们可以使用 decay
动画轻松复制它。
中 stopDrag
,替换 handleX.stop()
为 momentumScroll(x)
。
然后,在 snapHandleToEnd
函数后面的行中,添加一个名为 的新函数 momentumScroll
:
const momentumScroll = (x) => decay({ from: x, velocity: handleX.getVelocity() }).start(handleX);
现在,如果你扔把手,它会逐渐停止。它还将在滑块范围之外进行动画处理。我们可以通过将 clamp
转换器传递给 decay.pipe
方法来阻止这种情况:
const momentumScroll = (x) => decay({ from: x, velocity: handleX.getVelocity() }).pipe(clamp(0, rangeWidth)) .start(handleX);
结论
使用不同的 Popmotion 功能的组合,我们可以创建一个比平常更具生命力和趣味性的洗涤器。
通过使用 pipe
,我们将简单的纯函数组合成更复杂的行为,同时让组合部分可测试和可重用。
- 标记
- 导入函数
- 选择元素
- 事件监听器
- 移动手柄
- 弹簧边界
- 动量滚动