react-parabola
Version:
Parabolic motion of an object
215 lines (184 loc) • 4.86 kB
JSX
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const nil = () => false;
/*
* Parabola 组件参数说明:
* - start 起始点坐标/宽高
* - end 终点坐标/宽高
* - duration 运动时间
* - rate 取值范围[0, 1],确定极值点 x 坐标位置((end.x - start.x) * rate)
* - top 确定极值点 y 坐标位置 (end.y - top)
* - delay 动画开始之前延迟时间
* - onBeforeStart 动画开始之前事件
* - onEnd 动画结束事件
* - onAfterEnd 动画销毁之后事件
*/
export default class Parabola extends Component {
static defaultProps = {
rate: 1,
duration: 800,
top: 0,
delay: 0,
children: null,
onEnd: nil,
onBeforeStart: nil,
onAfterEnd: nil,
}
static propTypes = {
rate: PropTypes.number,
start: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
}).isRequired,
end: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
}).isRequired,
duration: PropTypes.number,
top: PropTypes.number,
delay: PropTypes.number,
children: PropTypes.element,
onEnd: PropTypes.func,
onBeforeStart: PropTypes.func,
onAfterEnd: PropTypes.func,
}
/*
* 根据极值点和另外一点计算抛物线参数
* 抛物线顶点式方程:y=a(x-h)^2+k,其中 (h, k) 为 极值点坐标
*/
static calculate(extreme, point) {
const { x: ex, y: ey } = extreme;
const { x, y } = point;
const a = (y - ey) / ((x - ex) ** 2);
const h = ex;
const k = ey;
return { a, h, k };
}
constructor(props) {
super(props);
// 初始状态
this.state = {
translateX: 0,
translateY: 0,
scaleX: 1,
scaleY: 1,
animationEnd: false,
};
this.startTime = new Date();
this.initParabolas();
}
componentDidMount() {
this.beforeStart();
this.draw();
}
componentWillUnmount() {
this.afterEnd();
}
draw() {
const { delay } = this.props;
setTimeout(() => {
this.update();
this.startTime = new Date();
}, delay);
}
finish() {
const { onEnd } = this.props;
if (onEnd) {
onEnd();
}
}
beforeStart() {
const { onBeforeStart } = this.props;
if (onBeforeStart) {
onBeforeStart();
}
}
afterEnd() {
const { onAfterEnd } = this.props;
if (onAfterEnd) {
onAfterEnd();
}
}
/*
* 坐标系统转换,将全局坐标系转换为相对坐标系
*/
initParabolas() {
const {
rate, top, start, end,
} = this.props;
const left = { // 起点
x: 0,
y: 0,
};
const right = { // 终点
x: end.x - start.x,
y: end.y - start.y,
};
const extreme = { // 极值点
x: rate * right.x,
y: Math.min(right.y, left.y) - top,
};
const lParabola = Parabola.calculate(extreme, left); // 极值点左侧抛物线参数
const rParabola = Parabola.calculate(extreme, right); // 极值点右侧抛物线参数
this.parabolas = { lParabola, rParabola };
return this.parabolas;
}
update() {
const {
duration, rate, start, end,
} = this.props;
let { animationEnd } = this.state;
let interval = Date.now() - this.startTime;
if (interval > duration) {
if (animationEnd) {
return this.finish();
}
interval = duration;
animationEnd = true;
}
const percent = interval / duration;
const { lParabola, rParabola } = this.parabolas;
const { a, h, k } = rate > percent ? lParabola : rParabola;
// 计算位置
const x = percent * (end.x - start.x);
const y = (a * ((x - h) ** 2)) + k;
// 计算缩放
const scaleX = 1 - (percent * (1 - ((end.width || 1) / (start.width || 1))));
const scaleY = 1 - (percent * (1 - ((end.height || 1) / (start.height || 1))));
this.setState({
translateX: x,
translateY: y,
scaleX,
scaleY,
animationEnd,
});
// 刷新
requestAnimationFrame(this.update.bind(this));
return false;
}
render() {
const {
translateX, translateY, scaleX, scaleY,
} = this.state;
const { start, children } = this.props;
return (
<div
style={{
position: 'absolute',
left: `${start.x}px`,
top: `${start.y}px`,
width: `${start.width}px`,
height: `${start.height}px`,
transform: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`,
transformOrigin: '0 0',
}}
>
{ children }
</div>
);
}
}