洗涤标志下载
在Popmotion入门系列的第一部分中,我们学习了如何使用基于时间的动画,例如tween
和keyframes
。 我们还学习了如何使用Performance styler
在DOM上使用这些动画。
在第二部分中,我们学习了如何使用pointer
跟踪和记录velocity
。 然后,我们使用它来驱动基于速度的动画spring
, decay
和physics
。
在最后一部分中,我们将创建一个scrubber小部件,并将使用它来清理keyframes
动画。 我们将通过指针跟踪以及spring
和decay
的组合来制作小部件本身,以使其比普通的洗涤器更具内在的感觉。
自己尝试:
入门
标记
首先, 将此CodePen派生为HTML模板。 和以前一样,因为这是一个中级教程,所以我不会涉及所有内容。
需要注意的主要变化是洗涤塔上的手柄由两个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
:
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');
关键帧动画
对于我们的可擦除动画,我们将使用keyframes
使.box
从左向右移动。 但是,我们可以使用本教程稍后概述的相同方法轻松地淡化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轴
现在该使用pointer
拖动洗涤器手柄了。 在上一教程中,我们同时使用x
和y
属性,但使用洗涤器时,我们只需要x
。
我们更喜欢保持代码的可重用性,并且跟踪单个pointer
轴是相当普遍的用例。 因此,让我们创建一个新的函数,想象中的是pointerX
。
它会像pointer
一样工作,除了它将仅使用一个数字作为其参数并仅输出一个数字( x
):
const pointerX = (x) => pointer({ x }).pipe(xy => xy.x);
在这里,您可以看到我们正在使用一种称为pipe
的pointer
方法。 到目前为止,我们在所有Popmotion动作(包括keyframes
上都可以使用pipe
。
pipe
接受多种功能。 start
操作后,所有输出将依次轮流通过这些函数中的每一个,然后才提供用于start
的update
函数。
在这种情况下,我们的功能很简单:
xy => xy.x
它所做的只是获取通常由pointer
输出的{ x, y }
对象并仅返回x
轴。
事件监听器
在开始使用新的pointerX
函数进行跟踪之前,我们需要知道用户是否已开始按下手柄。
在上一教程中,我们使用了传统的addEventListener
函数。 这次,我们将使用另一个名为listen
Popmotion函数。 listen
还提供了pipe
方法以及对所有操作方法的访问,但是我们在这里不打算使用它。
listen
允许我们使用单个函数将事件侦听器添加到多个事件,类似于jQuery。 因此,我们可以将前面的四个事件侦听器压缩为两个:
listen(handle, 'mousedown touchstart').start(startDrag);
listen(document, 'mouseup touchend').start(stopDrag);
移动手柄
稍后我们将需要手柄的x velocity
,因此让它成为一个value
,正如我们在上一教程中所学的那样,它可以查询速度。 在定义handleStyler
之后的handleStyler
,添加:
const handleX = value(0, handleStyler.set('x'));
现在,我们可以添加startDrag
和stopDrag
函数:
const startDrag = () => pointerX(handleX.get())
.start(handleX);
const stopDrag = () => handleX.stop();
现在,可以将手柄擦洗到滑块的边界之外,但是稍后我们将再次讨论。
擦洗
现在我们有了一个视觉上可以正常工作的清理器,但是我们并没有清理实际的动画。
每个value
都有一个subscribe
方法。 这使我们可以在value
更改时附加多个订户来触发。 每当handleX
更新时,我们都希望查找keyframes
动画。
首先,测量滑块。 在我们定义range
之后的那一行,添加:
const rangeWidth = range.getBoundingClientRect().width;
keyframes.seek
接受从0
到1
表示的进度值,而我们的handleX
设置为从0
到rangeWidth
像素值。
通过将当前像素测量值除以rangeWidth
我们可以将像素测量值转换为0
到1
范围。 在boxAnimation.pause()
之后的行上,添加以下订阅方法:
handleX.subscribe(v => boxAnimation.seek(v / rangeWidth));
现在,如果您使用滑块,则动画将成功擦除!
额外里程
Spring边界
洗涤器仍然可以拉出整个范围的边界。 为了解决这个问题,我们可以简单地使用clamp
函数来确保我们不会输出超出0, rangeWidth
值。
取而代之的是,我们将执行额外的步骤并将弹簧附加到滑块的末端。 当用户将手柄拉到允许范围之外时,它将向后拉动。 如果用户在范围之外释放手柄,我们可以使用spring
动画将其捕捉回去。
我们将使此过程成为可提供给pointerX
pipe
方法的单个函数。 通过创建一个可重用的函数,我们可以将这段代码与任何Popmotion动画一起使用,并具有可配置的范围和弹性强度。
首先,让我们在最左侧的极限处施加弹簧。 我们将使用两个转换器 ,即conditional
转换器和linearSpring
。
const springRange = (min, max, strength) => conditional(
v => v < min,
linearSpring(strength, min)
);
conditional
有两个功能,一个断言和一个转换器。 断言接收提供的值并返回true
或false
。 如果返回true
,将向第二个函数提供要转换和返回的值。
在这种情况下,断言是说:“如果提供的值小于min
,则将该值通过linearSpring
变压器传递。” linearSpring
是一个简单的弹簧函数,与physics
或spring
动画不同,它没有时间概念。 给它提供一个strength
和一个target
,它将创建一个函数,该函数以定义的强度向目标“吸引”任何给定值。
将我们的startDrag
函数替换为:
const startDrag = () => pointerX(handleX.get())
.pipe(springRange(0, rangeWidth, 0.1))
.start(handleX);
现在,我们通过springRange
函数传递指针的x
偏移量,因此,如果将手柄拖动到最左侧,您会注意到它向后拖拉。
将其应用于最右侧是使用独立pipe
功能将第二个conditional
与第一个conditional
组成的问题:
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
,用momentumScroll(x)
handleX.stop()
momentumScroll(x)
替换handleX.stop()
momentumScroll(x)
。
然后,在snapHandleToEnd
函数之后的snapHandleToEnd
,添加一个称为momentumScroll
snapHandleToEnd
的新函数:
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
,我们可以将简单的纯函数组合为更复杂的行为,同时使复合块可测试和可重用。
如何应对这些挑战:
洗涤标志下载