函数式编程能干什么(二)-- 用 Rx.js 写个抛物线动画

栏目: 编程语言 · 发布时间: 6年前

内容简介:昨天在掘金看到一篇文章,内容是用原生 JS 写抛物线动画。看完觉得挺有趣,很适合用 Rx.js 来重现,于是有了这篇文章。本文默认你已经掌握了 Rx.js 的基本概念和操作。若你还没掌握,推荐先看一些入门资料。动画的本质就是页面元素随着时间的持续,在特定时间点改变自身在页面中的坐标位置。这个很适合用响应式编程中“流”的概念来表达。我们需要将动画的持续时间(本文只考虑时间限定的情况)内根据浏览器

昨天在掘金看到一篇文章,内容是用原生 JS 写抛物线动画。看完觉得挺有趣,很适合用 Rx.js 来重现,于是有了这篇文章。

本文默认你已经掌握了 Rx.js 的基本概念和操作。若你还没掌握,推荐先看一些入门资料。

动画的本质就是页面元素随着时间的持续,在特定时间点改变自身在页面中的坐标位置。这个很适合用响应式编程中“流”的概念来表达。我们需要将动画的持续时间(本文只考虑时间限定的情况)内根据浏览器 requestAnimateFrame API 所允许的时间点映射成一个个节点,然后在这一个个节点中改变物体的位置。这个关键一步做好了,剩下的诸如 easing 曲线和加速度等都好解决了。

来看怎么解决第一个问题。先上代码:

// 首先我就把所有 Observable 和操作符导入了,接下来就省略了
import { interval, animationFrameScheduler, fromEvent, defer, merge } from "rxjs";
import { map, takeWhile, tap, flatMap } from "rxjs/operators";

function duration(ms) {
    return defer(() => {
        const start = Date.now();
        return interval(0, animationFrameScheduler).pipe(
            map(() => (Date.now() - start) / ms),
            takeWhile(n => n <= 1)
        );
    });
}
复制代码

defer 的作用是,只有当被订阅时,它才会根据提供给它的 Observable 工厂函数,生成新的 Observable。这样做的目的是, duration 需要为每一个订阅者提供新的 Observable。等下会看到它会在不同的地方被订阅。

首先在 defer 里面的 Observable 工厂函数前面记录当前时间戳。接下来下一行, interval 的作用是相隔指定时间段,释放一个行为(这个比较抽象,可以理解成告诉管道的下一个接收者要开始做事了)。 interval 接受两个参数,第二个参数是 Scheduler。默认的 Scheduler 是 async ,这里我们需要提供 animationFrameScheduler 。这样做的意思是,告诉 interval 每隔 0s 释放一次行为,这个行为由 animationFrameScheduler 调控。事实上后者不会真的每 0s 就释放一次,而是会通过 requestAnimationFrame 来获取浏览器的空闲时间(下一帧渲染之前),只有当浏览器有空了才会响应 interval 的指令。

然后接下来进入管道,第一个 map 意思是,把 interval 的指令映射成一个时间比例,该时间比例由当前时间,减去 interval 生成之前的时间,然后除以总时间,得到的是当前时间点占总时间长的比率。 takeWhile 指定一旦这个时间比例超过 1,就把 Observable 停掉。举个例子,本来指定了 3 秒,但是时间过了 4 秒,4/3 就大于 1 了,超过了动画指定时长。

最重要的部分就处理完了。

接下来计算每个时间点物体应该移动的距离:

const distance = d => t => d * t;
复制代码

参数 d 指的是总距离,t 指的是时间比率,就是我们在上一步算出来的。两者相乘就是每个时间点物体移动的距离了。注意,函数式编程里面的函数都要柯里化(回调函数不一定)。这样做的好处等下会看到。

然后取到 DOM 上的目标元素,对其进行位移:

const targetDiv = document.querySelector(".target");

const moveRight$ = duration(2000).pipe(
    map(distance(1000)),
    tap(x => (targetDiv.style.left = x + "px"))
);

const moveDown$ = duration(2000).pipe(
    map(distance(700)),
    tap(y => (targetDiv.style.top = y + "px"))
);
复制代码

这里写了两个流,分别是右移和下移,右移 1000px, 下移 700px。注意到我们把总距离传给 distance 函数后,它会返回新的函数,等着管道上游给它传时间比例 t,这就是柯里化的作用。

然后我们把两个流合并,就可以让物体同时右移和下移,也就是让它走对角线。

merge(moveRight$, moveDown$).subscribe()
复制代码

动画的第一阶段写完了,此时目标物体会从左上角到右下角做匀速直线运动。接下来我们要加上抛物线轨迹和重力加速度效果。

思考一下,抛物线的轨迹是水平移动和垂直移动速度不一致导致的,而加速度是由两者的速率变化导致的。前者可以用两者的函数关系来体现,后者可以用两者各自的 easing 函数来体现。我查了一下主流的 easing 函数,仿写了两个。

第一个是 easeInQuad

const easeInQuad = t => t * t;
复制代码

第二个是 easeInQuint

const easeInQuint = t => t * t * t * t * t * t;
复制代码

可以看出两者的函数关系是 y = Math.pow(x, 3) ,刚好是个抛物线。若想定制加速度和抛物线轨迹,也可以自己写。

接下来只用把 interval 里面的时间比例应用于各自的 easing 函数就行了。然后再加个按钮,只有点击按钮后,动画才开始。

一步到位完整代码:

const targetDiv = document.querySelector(".target");
const startBtn = document.querySelector("#start");


const startClick$ = fromEvent(startBtn, "click");

const easeInQuad = t => t * t;

const easeInQuint = t => t * t * t * t * t * t;

function duration(ms) {
    return defer(() => {
        const start = Date.now();
        return interval(0, animationFrameScheduler).pipe(
            map(() => (Date.now() - start) / ms),
            takeWhile(n => n <= 1)
        );
    });
}

const distance = d => t => d * t;

const moveDown$ = duration(1500).pipe(
    map(easeInQuint),
    map(distance(700)),
    tap(y => (targetDiv.style.top = y + "px"))
);

const moveRight$ = duration(1500).pipe(
    map(easeInQuad),
    map(distance(1000)),
    tap(x => (targetDiv.style.left = x + "px"))
);

startClick$.pipe(
    flatMap(() => merge(moveRight$, moveDown$))
).subscribe()
复制代码

线上效果在这里


以上所述就是小编给大家介绍的《函数式编程能干什么(二)-- 用 Rx.js 写个抛物线动画》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Measure What Matters

Measure What Matters

John Doerr / Portfolio / 2018-4-24 / GBP 19.67

In the fall of 1999, John Doerr met with the founders of a start-up he’d just given $11.8 million, the biggest investment of his career. Larry Page and Sergey Brin had amazing technology, entrepreneur......一起来看看 《Measure What Matters》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具