需求
项目中使用 Antd 表格组件, 勾选多行数据时, 通过点击添加或加入的方式实现购物车添加动画
- 表格中多选选中后加入到购物车中(右上角或指定位置购物车)
- 点击加入时, 需要有动画效果(更优的方案是抛物线或可配置的路径)
- 支持动画自定义
在线DEMO
更多参考React 动画
抛物线运动 JS 绘制购物车抛物线动画
Add to cart fly animation
实现抛物线的算法
更多animation参考
获取位置:
实现逻辑
- 首先实现一个定位的小球(可以是空的标签, 用颜色代替 也可以是一个自定义组件, 提供出API)
- 获取到目标元素在页面中的位置 offsetTop 和 offsetLeft的值
- 使用定位将子组件包裹在内, 外层使用相对定位, 而 cellball 使用绝对定位, 计算子元素(children)的位置从而计算出left 和 right 的值
- 定义好运行的动画 animation, ease即可(可以使用如 aniate.css中的动画效果)
- 根据目标元素 和运动元素的位置进行计算出需要偏移的量 使用 transform 结合 translate进行偏移, 支持 transform 其他的属性如 scale 等操作(不排除有图片作为运动元素)
- 需要提供通用的 context 组件进行包裹, 且可以提供获取到目标元素的方法, 比如提供 getTarget() => document.getElementById() 方式获取到目标元素的位置
API
| 属性 | 描述 | 类型 | 默认值 | | —- | —- | —- | —- | | getTarget | 获取目标元素的方法 | () => document.getElementById() | void(如果为空就需要指定一个默认值或者是必须使用提供的 context进行包裹) | | motionNode | 指定动画的节点 | ReactNode | 默认使用自带的小球, 需要在外层包裹一层用于定位 | |
| 运动动画 | animation | 执行动画的方式, linear | ease | 自定义的贝塞尔曲线等 | | | | | 支持 自定义的 transform运动, 排除translate 如果传递需要使用正则匹配掉 | | | 是否是抛物线方式 | | false, 当设置为 true时需要进行计算抛物线的轨迹 | | | 单次或批量 | | 单次: 只允许有一个节点进行动画
批量: 如table 选中多行时需要考虑是依次执行或者是同时执行, 需要提供额外的API | | | 触发方法 | | | | | 监听动画执行完毕效果 | | 当动画执行完毕后, 目标元素的接收动画是否需要执行 | | | 最大执行数量 | | 不可能无限制个数同时执行 |
初版实现
大致思路为了防止频繁的重绘和重排, 先对每一个需要添加动画的节点元素添加上指定运动元素
- 首先计算获取到目标元素的位置和高度宽度信息, 通常不是在同一个父组件下, 此时需要提供一个 context或者有其他更好的方法, 总之需要提供出目标元素的信息
- 对需要加入购物车的源节点上添加带动画的组件(内部包含有自身位置及大小, 且优先使用css添加 class实现基础的运动样式)
需要优化的点: 直线运动太过于单调, 虽然可以实现需求, 但对API的设定及效果优化仍然需要提高
CellBall.tsx
/**
* 表格渲染的小球
* 1. 可能需要使用 js 控制 keyframe 的路径值
* 2. 使用 定时器递增
* 3. 计算 children 的宽度 和高度, 更精准的计算到小球位置
* 4. 通常利用到图片, 如果封装到组件中使用时, 需要创建缩略图
* 5. 购物车如果分类展示, 也是列表时, 需要添加不同的分类或物品时新增一个购物车的目标元素
*/
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./animate.css";
export interface CellBallPropsInt {
children?: React.ReactNode;
targetPos: PosType;
fly: boolean;
}
export type PosType = {
top: number;
left: number;
};
// 元素高度, 宽度 位置信息
export type EleType = PosType & {
width: number;
height: number;
};
// function getOffset(targetPos: PosType, pos: PosType) {
// return {
// offsetLeft: targetPos.left - pos.left,
// offsetTop: pos.top - targetPos.top
// };
// }
const CellBall: React.FC<CellBallPropsInt> = props => {
const ballRef = React.useRef<any>();
const [pos, setPos] = React.useState<EleType>({
left: 0,
top: 0,
width: 0,
height: 0
});
const [{ left, top }, setLeftTop] = React.useState({ left: 0, top: 0 });
// const [{ offsetLeft, offsetTop }, setOffsetPos] = React.useState(
// getOffset(props.targetPos, pos)
// );
React.useEffect(() => {
if (ballRef.current) {
const { top, left, width, height } = (ReactDOM.findDOMNode(
ballRef.current
) as Element).getBoundingClientRect();
setPos({ left, top, width, height });
}
}, []);
/** 重新计算位置 */
// React.useEffect(() => {
// setOffsetPos(getOffset(props.targetPos, pos));
// }, [props.targetPos, pos]);
return (
<span ref={ballRef} className="check-ball-wrapper">
{props.children}
<span
className={`ball ${props.fly ? "show" : ""}`}
style={{
// -2, -1 为了和 checkbox 居中定位
// 计算 -16 是 checkbox 的高度 和宽度, 理应动态计算 children 的高度和宽度
left: props.fly
? props.targetPos.left
: pos.left - (pos.width - 16) / 2,
top: props.fly
? props.targetPos.top
: pos.top - (pos.height - 16 - 2) / 2
}}
/>
</span>
);
};
export default CellBall;
animate.css
/**
1. 样式调整
2. 支持自定义的 transition 动画效果
3. 如果可以的话引入比如 animate.css 等优秀的 css 动画库, 只引入比较常见的几种动画即可
4. 开放出更多 bezier 曲线
5. 如何计算出抛物线动画路径 ?
**/
.check-ball-wrapper {
position: relative;
z-index: 10;
}
.ball {
display: inline-block;
position: fixed;
z-index: -1;
height: 20px;
width: 20px;
border-radius: 100%;
background: blueviolet;
}
.show {
opacity: 1;
/* animation: fly 1s; */
transition: all cubic-bezier(0.28, -0.57, 0.27, 1.55) 2s;
}
@keyframes fly {
0% {
left: 0%;
}
100% {
left: 100%;
top: 100%;
}
}
暂定API
// 通过使用 getBoundingClientRect 获取到屏幕中元素的基础位置信息
type TargetPosType = {
top: number;
left: number;
/** 后续考虑计算问题 */
width?: number;
height?: number;
}
API | 描述 | 类型 | 默认值 |
---|---|---|---|
targetPos | 指定动画元素的位置信息 | TargetPosType | { top: 0, left: 0 } |
fly | 是否开启动画 | boolean | false |
后续设计
- 支持引入 animate.css 动画库的常见动画效果
- 支持抛物线的形式动画
- 支持图片缩略图(比如: 购物时的展示图片缩略图而不是小圆球)
- 假设购物车是多种不同商品的列表, 点击添加时,如果列表中已经存在, 则可以直接使用 targetPos 定位, 但如果不存在, 需要新构建一个目标节点同时获取到位置