Skip to content

Commit

Permalink
入场动画支持配置重复
Browse files Browse the repository at this point in the history
  • Loading branch information
qkiroc committed Dec 9, 2024
1 parent a937d25 commit f5dff4a
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 54 deletions.
133 changes: 82 additions & 51 deletions packages/amis-core/src/components/Animations.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import {useEffect, useRef} from 'react';
import React, {useState, useEffect, useRef} from 'react';
import {CSSTransition} from 'react-transition-group';
import {Schema} from '../types';
import {formateId} from '../utils';
Expand All @@ -15,98 +14,130 @@ function Animations({
component: any;
show: boolean;
}) {
const animationClassNames = useRef<{
appear?: string;
enter?: string;
exit?: string;
}>({});
const animationTimeout = useRef<{
enter?: number;
exit?: number;
}>({});
const ref = useRef<HTMLDivElement>(null);
const [animationShow, setAnimationShow] = React.useState(false);
const idRef = useRef<string>(formateId(schema.id));
const id = idRef.current;
const {enter} = schema.animations || {};
const [animationShow, setAnimationShow] = React.useState(!enter?.inView);
const [placeholderShow, setPlaceholderShow] = React.useState(!!enter?.inView);

useEffect(() => {
const [animationClassNames] = useState(() => {
const animations = schema?.animations;
const animationClassNames = {
appear: '',
enter: '',
exit: ''
};
if (animations) {
let id = schema.id;
id = formateId(id);
if (animations.enter) {
animationTimeout.current.enter =
animationClassNames.enter = `${animations.enter.type}-${id}-enter`;
animationClassNames.appear = animationClassNames.enter;
}
if (animations.exit) {
animationClassNames.exit = `${animations.exit.type}-${id}-exit`;
}
}
return animationClassNames;
});
const [animationTimeout] = useState(() => {
const animations = schema?.animations;
const animationTimeout = {
enter: 1000,
exit: 1000
};
if (animations) {
if (animations.enter) {
animationTimeout.enter =
((animations.enter.duration || 1) + (animations.enter.delay || 0)) *
1000;
animationClassNames.current.enter = `${animations.enter.type}-${id}-enter`;
animationClassNames.current.appear = animationClassNames.current.enter;
}
if (animations.exit) {
animationTimeout.current.exit =
animationTimeout.exit =
((animations.exit.duration || 1) + (animations.exit.delay || 0)) *
1000;
animationClassNames.current.exit = `${animations.exit.type}-${id}-exit`;
}
createAnimationStyle(id, animations);
}
return animationTimeout;
});

useEffect(() => {
createAnimationStyle(id, schema.animations!);
return () => {
if (schema.animations) {
let {id} = schema;
id = formateId(id);
styleManager.removeStyles(id);
}
};
}, []);

useEffect(() => {
const observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach(entry => {
function refFn(ref: HTMLDivElement) {
if (ref) {
const observer = new IntersectionObserver(
([entry], observer) => {
if (entry.isIntersecting) {
setAnimationShow(true);
setPlaceholderShow(false);
observer.disconnect();
}
});
},
{
root: null,
rootMargin: '0px',
threshold: 0.1
},
{
root: null,
rootMargin: '0px',
threshold: 0.1
}
);
if (ref) {
observer.observe(ref);
}
);
if (ref.current) {
observer.observe(ref.current);
}
}, [show]);
}

function addAnimationAttention(node: HTMLElement) {
const {attention} = schema.animations || {};
function handleEntered(node: HTMLElement) {
const {attention, exit, enter} = schema.animations || {};
if (attention) {
let {id} = schema;
id = formateId(id);
node.classList.add(`${attention.type}-${id}-attention`);
}

if (exit?.outView || enter?.repeat) {
const observer = new IntersectionObserver(
([entry], observer) => {
if (!entry.isIntersecting) {
setAnimationShow(false);
observer.disconnect();
}
},
{
root: null,
rootMargin: '0px',
threshold: 0.1
}
);
observer.observe(node);
}
}
function removeAnimationAttention(node: HTMLElement) {
function handleExit(node: HTMLElement) {
const {attention} = schema.animations || {};
if (attention) {
let {id} = schema;
id = formateId(id);
node.classList.remove(`${attention.type}-${id}-attention`);
}
}

function handleExited() {
setPlaceholderShow(true);
}

return (
<>
{!animationShow && show && (
<div ref={ref} className="amis-animation-placeholder">
{!animationShow && show && placeholderShow && (
<div ref={refFn} className="amis-animation-placeholder">
{component}
</div>
)}
<CSSTransition
in={animationShow && show}
timeout={animationTimeout.current}
classNames={animationClassNames.current}
onEntered={addAnimationAttention}
onExit={removeAnimationAttention}
timeout={animationTimeout}
classNames={animationClassNames}
onEntered={handleEntered}
onExit={handleExit}
onExited={handleExited}
appear
unmountOnExit
>
Expand Down
4 changes: 4 additions & 0 deletions packages/amis-core/src/utils/animations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export interface AnimationsProps {
type: string;
duration?: number;
delay?: number;
repeat?: boolean;
inView?: boolean;
};
attention?: {
type: string;
Expand All @@ -16,6 +18,8 @@ export interface AnimationsProps {
type: string;
duration?: number;
delay?: number;
repeat?: boolean;
outView?: boolean;
};
}

Expand Down
3 changes: 3 additions & 0 deletions packages/amis-core/src/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2437,6 +2437,9 @@ export function supportsMjs() {
}

export function formateId(id: string) {
if (!id) {
return guid();
}
// 将className非法字符替换为短横线
id = id.replace(/[^a-zA-Z0-9-]/g, '-');
// 将连续的-替换为单个-
Expand Down
37 changes: 34 additions & 3 deletions packages/amis-editor/src/tpl/style.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import {setSchemaTpl, getSchemaTpl, defaultValue} from 'amis-editor-core';
import {
setSchemaTpl,
getSchemaTpl,
defaultValue,
tipedLabel
} from 'amis-editor-core';
import {createAnimationStyle, formateId, type SchemaCollection} from 'amis';
import kebabCase from 'lodash/kebabCase';
import {styleManager} from 'amis-core';
Expand Down Expand Up @@ -1532,7 +1537,26 @@ setSchemaTpl('animation', () => {
return {
title: '动画',
body: [
...animation('enter', '进入动画'),
...animation('enter', '进入动画', [
{
label: tipedLabel('可见时触发', '组件进入可见区域才触发进入动画'),
type: 'switch',
name: 'animations.enter.inView',
value: true,
onChange: (value: any, oldValue: any, obj: any, props: any) => {
if (value === false) {
props.setValueByName('animations.enter.repeat', false);
}
}
},
{
label: tipedLabel('重复', '组件再次进入可见区域时重复播放动画'),
type: 'switch',
name: 'animations.enter.repeat',
visibleOn: 'animations.enter.inView',
value: false
}
]),
...animation('attention', '强调动画', [
{
label: '重复',
Expand All @@ -1548,7 +1572,14 @@ setSchemaTpl('animation', () => {
]
}
]),
...animation('exit', '退出动画')
...animation('exit', '退出动画', [
{
label: tipedLabel('不可见时触发', '组件退出可见区域触发进入动画'),
type: 'switch',
name: 'animations.exit.outView',
value: true
}
])
]
};
});

0 comments on commit f5dff4a

Please sign in to comment.