Merge pull request #25 from H5-Dooring/animate-patch

Animate patch
This commit is contained in:
yehuozhili
2022-01-30 11:33:10 +08:00
committed by GitHub
8 changed files with 310 additions and 144 deletions

View File

@@ -1,5 +1,8 @@
name: build name: build
on: [push] on:
push:
branches:
- main
jobs: jobs:
build-and-deploy: build-and-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -118,7 +118,7 @@ const repeat = ['1', '2', '3', '4', '5', 'infinite'];
const timeFunction: Record<string, string> = { const timeFunction: Record<string, string> = {
: 'linear', : 'linear',
: 'ease in', : 'ease-in',
}; };
let lastAnimate: AnimateItem[] = []; let lastAnimate: AnimateItem[] = [];
@@ -302,17 +302,19 @@ function AnimateControl(props: AnimateControlProps) {
<Row style={{ padding: padding, justifyContent: 'space-around' }}> <Row style={{ padding: padding, justifyContent: 'space-around' }}>
{animate.length > 0 && ( {animate.length > 0 && (
<Button <Button
onClick={() => { onClick={async () => {
if (!isOmit) { if (!isOmit) {
isOmit = true; isOmit = true;
props.config.waitAnimate = true;
const cacheProps = animate; const cacheProps = animate;
await props.config.timelineNeedleConfig.resetFunc(false);
const data: IStoreData = deepCopy(store.getData()); const data: IStoreData = deepCopy(store.getData());
props.config.waitAnimate = true;
data.block.forEach((v) => { data.block.forEach((v) => {
if (v.id === props.current.id) { if (v.id === props.current.id) {
v.animate = []; v.animate = [];
} }
}); });
props.config.timelineNeedleConfig.status = 'pause';
store.setData(data); store.setData(data);
setTimeout(() => { setTimeout(() => {
const clone: IStoreData = deepCopy(store.getData()); const clone: IStoreData = deepCopy(store.getData());
@@ -323,10 +325,9 @@ function AnimateControl(props: AnimateControlProps) {
}); });
isOmit = false; isOmit = false;
props.config.waitAnimate = false; props.config.waitAnimate = false;
store.cleanLast(); props.config.timelineNeedleConfig.status = 'start';
store.setData(clone); store.setData(clone);
store.cleanLast(); store.cleanLast();
props.config.timelineNeedleConfig.resetFunc();
}); });
} }
}} }}

View File

@@ -8,6 +8,8 @@ import { transfer } from '../core/transfer';
import { UserConfig } from '../config'; import { UserConfig } from '../config';
import styles from '../index.less'; import styles from '../index.less';
import { RotateReset, RotateResizer } from '../core/rotateHandler'; import { RotateReset, RotateResizer } from '../core/rotateHandler';
import { mergeAnimate } from '../core/utils/animate';
interface BlockProps { interface BlockProps {
data: IBlockType; data: IBlockType;
context: 'edit' | 'preview'; context: 'edit' | 'preview';
@@ -95,39 +97,39 @@ function Blocks(props: PropsWithChildren<BlockProps>) {
props.data.fixed, props.data.fixed,
]); ]);
const animateProps: CSSProperties = useMemo(() => { const [force, animateForce] = useState(0);
const select: CSSProperties = {
animationName: '', useEffect(() => {
animationDelay: '', const fn = () => {
animationDuration: '', animateForce((p) => p + 1);
animationIterationCount: '',
// animationFillMode: 'forwards',// 这个属性和transform冲突
animationTimingFunction: '',
}; };
props.data.animate.forEach((v) => { props.config.blockForceUpdate.push(fn);
select.animationName = const unload = () => {
select.animationName === '' props.config.blockForceUpdate = props.config.blockForceUpdate.filter((v) => v !== fn);
? v.animationName };
: select.animationName + ',' + v.animationName; return () => {
select.animationDelay = unload();
select.animationDelay === '' };
? v.animationDelay + 's' }, [animateForce, props.config]);
: select.animationDelay + ',' + v.animationDelay + 's';
select.animationDuration = const [animateProps, animationEdit]: [CSSProperties, CSSProperties] = useMemo(() => {
select.animationDuration === '' const [normal, editProps] = mergeAnimate(props.data.animate, {
? v.animationDuration + 's' isPause: props.config.timelineNeedleConfig.status !== 'start' ? true : false,
: select.animationDuration + ',' + v.animationDuration + 's'; delay:
select.animationIterationCount = props.config.timelineNeedleConfig.status === 'stop'
select.animationIterationCount === '' ? props.config.timelineNeedleConfig.current
? v.animationIterationCount : 0,
: select.animationIterationCount + ',' + v.animationIterationCount;
select.animationTimingFunction =
select.animationTimingFunction === ''
? v.animationTimingFunction
: select.animationTimingFunction + ',' + v.animationTimingFunction;
}); });
return select; return [
}, [props.data.animate]); {
animation: normal,
},
{
animation: editProps,
},
];
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.data.animate, props.config.timelineNeedleConfig, force]);
const render = useMemo(() => { const render = useMemo(() => {
// 如果是编辑模式下,则需要包裹不能选中层,位移层,缩放控制层,平面移动层。 // 如果是编辑模式下,则需要包裹不能选中层,位移层,缩放控制层,平面移动层。
@@ -167,7 +169,7 @@ function Blocks(props: PropsWithChildren<BlockProps>) {
<div <div
style={{ style={{
...style, ...style,
...animateProps, ...animationEdit,
}} }}
> >
{state} {state}
@@ -180,7 +182,7 @@ function Blocks(props: PropsWithChildren<BlockProps>) {
pointerEvents: 'none', pointerEvents: 'none',
width: '100%', width: '100%',
height: '100%', height: '100%',
...animateProps, ...animationEdit,
}} }}
> >
{state} {state}
@@ -191,7 +193,7 @@ function Blocks(props: PropsWithChildren<BlockProps>) {
<span <span
style={{ style={{
pointerEvents: 'none', pointerEvents: 'none',
...animateProps, ...animationEdit,
}} }}
> >
{state} {state}
@@ -214,10 +216,9 @@ function Blocks(props: PropsWithChildren<BlockProps>) {
zIndex: props.data.zIndex, zIndex: props.data.zIndex,
display: props.data.display, display: props.data.display,
transform: `rotate(${props.data.rotate.value}deg)`, transform: `rotate(${props.data.rotate.value}deg)`,
...animateProps,
}} }}
> >
{state} <div style={{ ...animateProps }}>{state}</div>
</div> </div>
); );
} }
@@ -225,14 +226,15 @@ function Blocks(props: PropsWithChildren<BlockProps>) {
state, state,
props.context, props.context,
props.data, props.data,
props.config,
props.iframe, props.iframe,
props.config,
innerDragData, innerDragData,
animateProps, animationEdit,
previewState.top, previewState.top,
previewState.left, previewState.left,
previewState.width, previewState.width,
previewState.height, previewState.height,
animateProps,
]); ]);
return render; return render;
} }

View File

@@ -16,7 +16,9 @@ import {
EyeInvisibleOutlined, EyeInvisibleOutlined,
EyeOutlined, EyeOutlined,
MenuOutlined, MenuOutlined,
PauseCircleOutlined,
PlayCircleOutlined, PlayCircleOutlined,
ReloadOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { import {
TimeLineItem, TimeLineItem,
@@ -43,7 +45,10 @@ export interface TimeLineNeedleConfigType {
status: 'stop' | 'start' | 'pause'; status: 'stop' | 'start' | 'pause';
runFunc: Function; runFunc: Function;
resetFunc: Function; resetFunc: Function;
pauseFunc: Function;
current: number; current: number;
isRefresh: boolean;
setNeedle: Function;
} }
const animateTicker = new Array(iter).fill(1).map((_, y) => y); const animateTicker = new Array(iter).fill(1).map((_, y) => y);
@@ -165,9 +170,84 @@ const SortableList = SortableContainer(
let cacheBlock: IBlockType[] = []; let cacheBlock: IBlockType[] = [];
// const needleWidth = 2; const needleWidth = 2;
// const initialLeft = 20 - needleWidth / 2; const initialLeft = 20 - needleWidth / 2;
// let timer: number | null = null; const needleHeadWidth = 15;
const needleHeadHeight = 22;
let timer: number | null = null;
const needleState = {
isDrag: false,
startX: 0,
origin: 0,
};
const needleHeadEvent = (
setNeedle: React.Dispatch<React.SetStateAction<number>>,
config: UserConfig
) => {
return {
onMouseDown: async (e: React.MouseEvent) => {
e.persist();
e.stopPropagation();
if (
config.timelineNeedleConfig.status === 'start' ||
!config.timelineNeedleConfig.isRefresh
) {
await config.timelineNeedleConfig.resetFunc();
}
setNeedle((p) => {
needleState.origin = p;
return p;
});
needleState.isDrag = true;
needleState.startX = e.clientX;
config.blockForceUpdate.forEach((v) => {
v();
});
if (timer) {
window.clearInterval(timer);
}
},
};
};
export const needleMoveEvent = (config: UserConfig) => {
const setNeedle = config.timelineNeedleConfig.setNeedle;
return {
onMouseMove: async (e: React.MouseEvent) => {
if (needleState.isDrag) {
e.persist(); //不加这个很容易导致clientx为null
const diff = e.clientX - needleState.startX;
setNeedle(() => {
const shouldMoveX = needleState.origin + diff;
if (shouldMoveX < initialLeft) {
config.timelineNeedleConfig.current = 0;
return initialLeft;
} else if (shouldMoveX > ruleWidth) {
config.timelineNeedleConfig.current = (ruleWidth - initialLeft) / 20;
return ruleWidth;
} else {
config.timelineNeedleConfig.current = (shouldMoveX - initialLeft) / 20;
return shouldMoveX;
}
});
config.timelineNeedleConfig.status = 'stop';
config.blockForceUpdate.forEach((v) => {
v();
});
}
},
onMouseUp: () => {
needleState.isDrag = false;
needleState.startX = 0;
},
onDoubleClick: () => {
// 这个暂时搞不定,可以在起始点埋个点位得到坐标值进行计算。
},
};
};
export function TimeLine(props: TimeLineProps) { export function TimeLine(props: TimeLineProps) {
const store = props.config.getStore(); const store = props.config.getStore();
const data = store.getData().block; const data = store.getData().block;
@@ -223,72 +303,106 @@ export function TimeLine(props: TimeLineProps) {
} }
}, [props.config]); }, [props.config]);
// const [needle, setNeedle] = useState(initialLeft); const [needle, setNeedle] = useState(initialLeft);
//const needleStart = () => { const resetAnimate = async () => {
// props.config.timelineNeedleConfig.current = 0; // 重置动画后才能调整delay
// setNeedle(initialLeft); return new Promise<void>((res) => {
// //每过0.1秒移动2 if (!WAIT) {
// if (timer) { WAIT = true;
// window.clearInterval(timer); props.config.waitAnimate = true;
// } const cache = data.map((v) => {
// props.config.timelineNeedleConfig.status = 'start'; return v.animate;
// const cloneData: IStoreData = deepcopy(store.getData()); });
// store.setData(cloneData); const cloneData: IStoreData = deepcopy(store.getData());
// store.cleanLast(); cloneData.block.forEach((v) => {
// timer = window.setInterval(() => { v.animate = [];
// if (needle < ruleWidth) { });
// setNeedle((pre) => { store.setData(cloneData);
// props.config.timelineNeedleConfig.current = (pre - initialLeft) / 20; setTimeout(() => {
// console.log(props.config.timelineNeedleConfig.current); props.config.timelineNeedleConfig.status = 'pause';
// return pre + 2; const cloneData: IStoreData = deepcopy(store.getData());
// }); cloneData.block.forEach((v, i) => {
// } v.animate = cache[i];
// }, 100); });
// }; WAIT = false;
props.config.waitAnimate = false;
store.setData(cloneData);
store.cleanLast();
res();
});
}
});
};
// const needlePlay = async () => { const refreshBlock = () => {
// if (timer) { const cloneData: IStoreData = deepcopy(store.getData());
// window.clearInterval(timer); store.setData(cloneData);
// } store.cleanLast();
// await resetAnimate(); };
// setTimeout(() => {
// props.config.timelineNeedleConfig.status = 'pause';
// timer = window.setInterval(() => {
// if (needle < ruleWidth) {
// setNeedle((pre) => {
// props.config.timelineNeedleConfig.current = (pre - initialLeft) / 20;
// return pre + 2;
// });
// }
// }, 100);
// });
// };
// const needleReset = () => { const needlePlay = async () => {
// if (timer) { if (timer) {
// window.clearInterval(timer); window.clearInterval(timer);
// } }
// props.config.timelineNeedleConfig.status = 'pause'; //判断如果status不是pause则要执行reset
// props.config.timelineNeedleConfig.current = 0; if (props.config.timelineNeedleConfig.status !== 'pause') {
// resetAnimate(); await needleReset();
// setNeedle(initialLeft); }
// store.cleanLast(); props.config.timelineNeedleConfig.status = 'start';
// }; props.config.timelineNeedleConfig.isRefresh = false;
refreshBlock();
setTimeout(() => {
timer = window.setInterval(() => {
setNeedle((pre) => {
if (pre < ruleWidth) {
props.config.timelineNeedleConfig.current = (pre - initialLeft) / 20;
return pre + 2;
} else {
if (timer) {
window.clearInterval(timer);
}
return pre;
}
});
// props.config.blockForceUpdate.forEach((v) => v());
}, 100);
});
};
// const needlePause = () => { const needleReset = async (needResetAnimate = true) => {
// props.config.timelineNeedleConfig.status = 'pause'; if (timer) {
// if (timer) { window.clearInterval(timer);
// window.clearInterval(timer); }
// } props.config.timelineNeedleConfig.status = 'start';
// const cloneData: IStoreData = deepcopy(store.getData()); if (needResetAnimate) {
// store.setData(cloneData); await resetAnimate();
// store.cleanLast(); }
// }; return new Promise<void>((res) => {
setTimeout(() => {
props.config.timelineNeedleConfig.status = 'pause';
props.config.timelineNeedleConfig.current = 0;
props.config.timelineNeedleConfig.isRefresh = true;
setNeedle(initialLeft);
refreshBlock();
res();
});
});
};
// props.config.timelineNeedleConfig.resetFunc = needleReset; const needlePause = () => {
// props.config.timelineNeedleConfig.runFunc = needleStart; props.config.timelineNeedleConfig.status = 'pause';
props.config.timelineNeedleConfig.isRefresh = false;
if (timer) {
window.clearInterval(timer);
}
refreshBlock();
};
props.config.timelineNeedleConfig.resetFunc = needleReset;
props.config.timelineNeedleConfig.runFunc = needlePlay;
props.config.timelineNeedleConfig.pauseFunc = needlePause;
props.config.timelineNeedleConfig.setNeedle = setNeedle;
return ( return (
<div <div
className={`${props.classes} ant-menu yh-timeline-wrap`} className={`${props.classes} ant-menu yh-timeline-wrap`}
@@ -330,14 +444,19 @@ export function TimeLine(props: TimeLineProps) {
textAlign: 'right', textAlign: 'right',
}} }}
> >
{/* <span <span
style={{ style={{
display: 'inline-block', display: 'inline-block',
cursor: 'pointer', cursor: 'pointer',
marginRight: '10px', marginRight: '10px',
}} }}
title="reset"
> >
<ReloadOutlined onClick={() => needleReset()} /> <ReloadOutlined
onClick={() => {
needleReset();
}}
/>
</span> </span>
<span <span
style={{ style={{
@@ -345,9 +464,14 @@ export function TimeLine(props: TimeLineProps) {
cursor: 'pointer', cursor: 'pointer',
marginRight: '10px', marginRight: '10px',
}} }}
title="pause"
> >
<PauseCircleOutlined onClick={() => needlePause()} /> <PauseCircleOutlined
</span> */} onClick={() => {
needlePause();
}}
/>
</span>
<span <span
title="play" title="play"
style={{ style={{
@@ -356,29 +480,7 @@ export function TimeLine(props: TimeLineProps) {
cursor: 'pointer', cursor: 'pointer',
}} }}
onClick={() => { onClick={() => {
//缓存所有animate后执行 needlePlay();
if (!WAIT) {
WAIT = true;
props.config.waitAnimate = true;
const cache = data.map((v) => {
return v.animate;
});
const cloneData: IStoreData = deepcopy(store.getData());
cloneData.block.forEach((v) => {
v.animate = [];
});
store.setData(cloneData);
setTimeout(() => {
const cloneData: IStoreData = deepcopy(store.getData());
cloneData.block.forEach((v, i) => {
v.animate = cache[i];
});
WAIT = false;
props.config.waitAnimate = false;
store.setData(cloneData);
store.cleanLast();
});
}
}} }}
> >
<PlayCircleOutlined /> <PlayCircleOutlined />
@@ -396,7 +498,25 @@ export function TimeLine(props: TimeLineProps) {
position: 'relative', position: 'relative',
}} }}
> >
{/* <div <div
className="yh-timeline-needle-head"
style={{
position: 'absolute',
transform: `translate(-${scrollx}px, 0px)`,
width: needleHeadWidth,
height: needleHeadHeight,
backgroundColor: '#ff5722',
zIndex: 3,
left: needle - needleHeadWidth / 2,
transition: 'left linear',
willChange: 'left',
borderRadius: '2px',
cursor: 'col-resize',
}}
{...needleHeadEvent(setNeedle, props.config)}
></div>
<div
className="yh-timeline-needle"
style={{ style={{
position: 'absolute', position: 'absolute',
transform: `translate(-${scrollx}px, 0px)`, transform: `translate(-${scrollx}px, 0px)`,
@@ -407,8 +527,9 @@ export function TimeLine(props: TimeLineProps) {
left: needle, left: needle,
transition: 'left linear', transition: 'left linear',
willChange: 'left', willChange: 'left',
pointerEvents: 'none',
}} }}
></div> */} ></div>
<div <div
style={{ style={{
display: 'flex', display: 'flex',

View File

@@ -60,7 +60,7 @@ const resizeMouseDown = (
left: boolean left: boolean
) => { ) => {
e.stopPropagation(); e.stopPropagation();
resizeState.startX = e.screenX; resizeState.startX = e.clientX;
resizeState.uid = v.uid; resizeState.uid = v.uid;
resizeState.isMove = true; resizeState.isMove = true;
resizeState.left = left; resizeState.left = left;
@@ -73,7 +73,7 @@ export const TimeLineItemMouseMove = function (
) { ) {
if (moveState.isMove) { if (moveState.isMove) {
//修改源属性 //修改源属性
const diff = e.screenX - moveState.startX; const diff = e.clientX - moveState.startX;
animate.forEach((v) => { animate.forEach((v) => {
if (v.uid === moveState.uid) { if (v.uid === moveState.uid) {
const f = parseFloat((v.animationDelay + diff / times).toFixed(1)); const f = parseFloat((v.animationDelay + diff / times).toFixed(1));
@@ -81,9 +81,9 @@ export const TimeLineItemMouseMove = function (
forceUpdate((p) => p + 1); forceUpdate((p) => p + 1);
} }
}); });
moveState.startX = e.screenX; moveState.startX = e.clientX;
} else if (resizeState.isMove) { } else if (resizeState.isMove) {
const diff = e.screenX - resizeState.startX; const diff = e.clientX - resizeState.startX;
if (resizeState.left) { if (resizeState.left) {
animate.forEach((v) => { animate.forEach((v) => {
if (v.uid === resizeState.uid) { if (v.uid === resizeState.uid) {
@@ -107,7 +107,7 @@ export const TimeLineItemMouseMove = function (
} }
}); });
} }
resizeState.startX = e.screenX; resizeState.startX = e.clientX;
} }
}; };
export const TimeLineItemMouseOver = function () { export const TimeLineItemMouseOver = function () {
@@ -142,7 +142,7 @@ export function TimeLineItem(props: TimeLineItemProps) {
<div <div
key={v.uid} key={v.uid}
onMouseDown={(e) => { onMouseDown={(e) => {
moveState.startX = e.screenX; moveState.startX = e.clientX;
moveState.uid = v.uid; moveState.uid = v.uid;
moveState.isMove = true; moveState.isMove = true;
}} }}
@@ -158,12 +158,14 @@ export function TimeLineItem(props: TimeLineItemProps) {
}} }}
> >
<div <div
className="yh-timeline-item-left"
style={{ ...commonCss, left: -square }} style={{ ...commonCss, left: -square }}
onMouseDown={(e) => { onMouseDown={(e) => {
resizeMouseDown(e, v, true); resizeMouseDown(e, v, true);
}} }}
></div> ></div>
<div <div
className="yh-timeline-item-right"
style={{ ...commonCss, right: -square }} style={{ ...commonCss, right: -square }}
onMouseDown={(e) => { onMouseDown={(e) => {
resizeMouseDown(e, v, false); resizeMouseDown(e, v, false);

View File

@@ -356,11 +356,15 @@ export class UserConfig {
scrollDom: null, scrollDom: null,
}; };
public timelineNeedleConfig: TimeLineNeedleConfigType = { public timelineNeedleConfig: TimeLineNeedleConfigType = {
status: 'stop', status: 'start',
runFunc: () => {}, runFunc: () => {},
resetFunc: () => {}, resetFunc: () => {},
pauseFunc: () => {},
setNeedle: () => {},
current: 0, current: 0,
isRefresh: true,
}; };
public blockForceUpdate: Array<Function> = [];
public waitAnimate = false; public waitAnimate = false;
public wrapperMoveState = wrapperMoveState; public wrapperMoveState = wrapperMoveState;
public iframeWrapperMoveState = iframeWrapperMoveState; public iframeWrapperMoveState = iframeWrapperMoveState;

View File

@@ -1,4 +1,4 @@
import { RefObject } from 'react'; import React, { RefObject } from 'react';
import { blockFocus, containerFocusRemove } from '../focusHandler'; import { blockFocus, containerFocusRemove } from '../focusHandler';
import { marklineConfig } from '../markline/marklineConfig'; import { marklineConfig } from '../markline/marklineConfig';
import { resizerMouseMove, resizerMouseUp } from '../resizeHandler'; import { resizerMouseMove, resizerMouseUp } from '../resizeHandler';
@@ -14,6 +14,7 @@ import { rotateMouseMove, rotateMouseUp } from '../rotateHandler';
import { specialCoList } from '../utils/special'; import { specialCoList } from '../utils/special';
import { marklineState } from '../markline/state'; import { marklineState } from '../markline/state';
import { itemHeight } from '../../components/timeLine/timelineItem'; import { itemHeight } from '../../components/timeLine/timelineItem';
import { needleMoveEvent } from '../../components/timeLine/timeline';
export const innerDrag = function ( export const innerDrag = function (
item: IBlockType, item: IBlockType,
@@ -150,6 +151,7 @@ export const innerContainerDragUp = function (config: UserConfig) {
marklineState.sortRight = null; marklineState.sortRight = null;
marklineState.sortBottom = null; marklineState.sortBottom = null;
iframeWrapperMove(config); iframeWrapperMove(config);
needleMoveEvent(config).onMouseUp();
wrapperMoveMouseUp(config); wrapperMoveMouseUp(config);
selectRangeMouseUp(e, config); selectRangeMouseUp(e, config);
if (innerDragState.ref && innerDragState.ref.current) { if (innerDragState.ref && innerDragState.ref.current) {
@@ -171,5 +173,8 @@ export const innerContainerDragUp = function (config: UserConfig) {
}; };
return { return {
onMouseUp, onMouseUp,
onMouseMove: (e: React.MouseEvent) => {
needleMoveEvent(config).onMouseMove(e);
},
}; };
}; };

View File

@@ -0,0 +1,28 @@
import { AnimateItem } from '../store/storetype';
// duration
// 1s ease 1s 1 forwards paused bounce ,
export function mergeAnimate(
animate: AnimateItem[],
config = {
delay: 0,
isPause: false,
}
) {
let configstr = '';
let str = '';
animate.forEach((v) => {
configstr =
(configstr === '' ? configstr : configstr + ',') +
`${v.animationDuration}s ${v.animationTimingFunction} ${(
v.animationDelay - config.delay
).toFixed(1)}s ${v.animationIterationCount} forwards ${
config.isPause ? 'paused' : 'running'
} ${v.animationName}`;
str =
(str === '' ? str : str + ',') +
`${v.animationDuration}s ${v.animationTimingFunction} ${v.animationDelay}s ${
v.animationIterationCount
} forwards ${'running'} ${v.animationName}`;
});
return [str, configstr];
}