• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

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

在 Popmotion 介绍系列的第一部分,我们学习了如何使用 基于时间的动画,例如 tween和 。我们还学习了如何在dom上使用这些动画,使用 performant ,keyframesstyler。

在第二部分,我们学习了如何使用 pointertracking 和 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接受多种功能。当操作被 started 时,所有输出将依次通过这些函数中的每一个,在 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'));

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

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

现在,可以将手柄擦洗到滑块边界之外,但我们稍后会回到这一点。

擦洗

现在我们有了一个视觉功能的擦洗器,但我们并没有擦洗实际的动画。

每个 value人都有 subscribe方法。这允许我们附加多个订阅者以在value更改时触发。我们想 在更新keyframes时寻找动画 。handleX

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

const rangeWidth = range.getBoundingClientRect().width;

keyframes.seek接受从 0to 表示的进度值1,而 our handleX设置为从0to的像素值rangeWidth

我们可以 通过将当前像素测量值除以 来将像素测量值转换0为 范围在之后的行上 ,添加此订阅方法:1rangeWidthboxAnimation.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有两个函数,一个断言和一个转换器。断言接收提供的值并返回 trueor  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,我们将简单的纯函数组合成更复杂的行为,同时让组合部分可测试和可重用。


文章目录
  • 入门
    • 标记
    • 导入函数
    • 选择元素
  • 关键帧动画
  • 拖动x-axis
    • 事件监听器
    • 移动手柄
  • 擦洗
  • 额外一英里
    • 弹簧边界
    • 动量滚动
  • 结论