当前位置: 首页 > 工具软件 > Popmotion > 使用案例 >

Popmotion简介:自定义动画洗涤器

时浩波
2023-12-01

在Popmotion入门系列的第一部分中,我们学习了如何使用基于时间的动画,例如tweenkeyframes 。 我们还学习了如何使用Performance styler在DOM上使用这些动画。

在第二部分中,我们学习了如何使用pointer跟踪和记录velocity 。 然后,我们用它来驱动基于速度的动画springdecayphysics

在最后一部分中,我们将创建一个scrubber小部件,并使用它来清理keyframes动画。 我们将通过指针跟踪以及springdecay的组合来制作小部件本身,以使其比普通的洗涤器更具内在的感觉。

自己尝试一下:

入门

标记

首先, 将此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拖动洗涤器手柄了。 在上一教程中,我们同时使用xy属性,但使用洗涤器时,我们只需要x

我们更喜欢保持代码的可重用性,并且跟踪单个pointer轴是相当普遍的用例。 因此,让我们创建一个新的函数,想象中的是pointerX

它会像pointer一样工作,除了它仅将一个数字作为其参数并仅输出一个数字( x ):

const pointerX = (x) => pointer({ x }).pipe(xy => xy.x);

在这里,您可以看到我们正在使用一种称为pipepointer方法。 到目前为止,我们在所有Popmotion动作(包括keyframes上都可以使用pipe

pipe接受多种功能。 start操作后,所有输出将依次轮流通过这些功能中的每一个,然后才提供用于startupdate功能。

在这种情况下,我们的功能很简单:

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'));

现在,我们可以添加startDragstopDrag函数:

const startDrag = () => pointerX(handleX.get())
  .start(handleX);
  
const stopDrag = () => handleX.stop();

现在,可以将手柄擦洗到滑块的边界之外,但是稍后我们将再次讨论。

擦洗

现在我们有了一个视觉上可以正常工作的清理器,但是我们并没有清理实际的动画。

每个value都有一个subscribe方法。 这使我们可以在value更改时附加多个订户来触发。 每当handleX更新时,我们都希望查找keyframes动画。

首先,测量滑块。 在我们定义range之后的那一行,添加:

const rangeWidth = range.getBoundingClientRect().width;

keyframes.seek接受从01表示的进度值,而我们的handleX设置为从0rangeWidth像素值。

通过将当前像素测量值除以rangeWidth我们可以将像素测量值转换为01范围。 在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有两个功能,一个断言和一个转换器。 断言接收提供的值并返回truefalse 。 如果返回true ,将向第二个函数提供要转换并返回的值。

在这种情况下,断言是说:“如果提供的值小于min ,则将该值通过linearSpring变压器传递。” linearSpring是一个简单的弹簧函数,与physicsspring动画不同,它没有时间概念。 给它提供一个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这样的函数的另一个好处是它变得非常容易测试。 像所有转换器一样,它返回的函数是一个采用单个值的纯函数。 您可以测试此函数,以查看它是否通过了不超过minmax不变的值,以及是否对不存在的值应用了弹簧。

如果在范围之外放开手柄,它现在应该弹回范围内。 为此,我们需要调整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设置为0rangeWidth具体取决于手柄当前位于滑块的哪一侧。 通过发挥dampingstiffness ,您可以发挥各种不同的弹簧感。

动量滚动

我一直很欣赏iOS洗涤器的一个很好的感觉是,如果您松开手柄,它会逐渐变慢而不是停滞不前。 我们可以使用decay动画轻松地复制它。

stopDrag ,将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 ,我们可以将简单的纯函数组合为更复杂的行为,同时使复合块可测试和可重用。

如何应对这些挑战:

翻译自: https://code.tutsplus.com/tutorials/introduction-to-popmotion-part-3-custom-animation-scrubber--cms-30560

 类似资料: