文本动画教程
注意:此功能仅在 Konva v10.0.0 及更高版本中可用。
Konva 通过 charRenderFunc 属性提供了强大的文本动画功能。此函数允许你自定义每个字符的渲染方式,从而实现逐字符动画和效果。
var text = new Konva.Text({
x: 10,
y: 10,
text: 'AB',
fontSize: 20,
charRenderFunc: function ({ context, index }) {
if (index === 1) {
// 仅偏移第二个字符
context.translate(0, 10);
}
},
});
charRenderFunc 接收一个包含以下参数的上下文对象:
char- 正在渲染的实际字符字符串index- 字符在整个文本中的从零开始的索引x- 字符将要渲染的 X 坐标位置y- 字符将要渲染的 Y 坐标位置lineIndex- 包含该字符的行从零开始的索引column- 当前行内从零开始的列位置isLastInLine- 布尔值,指示这是否是其行中的最后一个字符width- 字符的宽度context- Canvas 2D 渲染上下文,用于应用变换、透明度、颜色等。
这允许你根据字符的位置和属性,对单个字符应用变换、透明度更改或其他效果。
- Vanilla
- React
- Vue
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// 我们将把每个字符的不透明度存储在一个数组中
const charOpacities = [];
const textNode = new Konva.Text({
x: window.innerWidth / 2 - 100,
y: window.innerHeight / 2 - 20,
text: 'ANIMATION',
fontSize: 40,
fontFamily: 'Arial',
fill: '#333',
charRenderFunc: function ({ context, index }) {
context.globalAlpha = charOpacities[index];
},
});
layer.add(textNode);
const anim = new Konva.Animation(function(frame) {
const time = frame.time;
const cycleDuration = 4000; // 4 秒总周期
const fadeInDuration = 1500; // 1.5 秒全部淡入
const holdDuration = 1000; // 1 秒保持
const fadeOutDuration = 1500; // 1.5 秒全部淡出
const cycleTime = time % cycleDuration;
for (let i = 0; i < textNode.text().length; i++) {
const charDelay = i * 150; // 字符之间延迟 150 毫秒
if (cycleTime < fadeInDuration) {
// 淡入阶段
const charStartTime = charDelay;
const charFadeTime = Math.max(0, cycleTime - charStartTime);
charOpacities[i] = Math.min(1, charFadeTime / 300);
} else if (cycleTime < fadeInDuration + holdDuration) {
// 保持阶段 - 所有字符可见
charOpacities[i] = 1;
} else {
// 淡出阶段
const fadeOutStart = fadeInDuration + holdDuration;
const charFadeOutDelay = i * 150; // 与淡入顺序相同
const charFadeOutTime = Math.max(0, cycleTime - fadeOutStart - charFadeOutDelay);
charOpacities[i] = Math.max(0, 1 - charFadeOutTime / 300);
}
}
}, layer);
anim.start();
import { Stage, Layer, Text } from 'react-konva';
import { useEffect, useRef } from 'react';
const App = () => {
const textRef = useRef(null);
const layerRef = useRef(null);
const charOpacitiesRef = useRef([]);
useEffect(() => {
const anim = new Konva.Animation((frame) => {
const time = frame.time;
const cycleDuration = 4000; // 4 秒总周期
const fadeInDuration = 1500; // 1.5 秒全部淡入
const holdDuration = 1000; // 1 秒保持
const fadeOutDuration = 1500; // 1.5 秒全部淡出
const cycleTime = time % cycleDuration;
for (let i = 0; i < textRef.current.text().length; i++) {
const charDelay = i * 150; // 字符之间延迟 150 毫秒
if (cycleTime < fadeInDuration) {
// 淡入阶段
const charStartTime = charDelay;
const charFadeTime = Math.max(0, cycleTime - charStartTime);
charOpacitiesRef.current[i] = Math.min(1, charFadeTime / 300);
} else if (cycleTime < fadeInDuration + holdDuration) {
// 保持阶段 - 所有字符可见
charOpacitiesRef.current[i] = 1;
} else {
// 淡出阶段
const fadeOutStart = fadeInDuration + holdDuration;
const charFadeOutDelay = i * 150; // 与淡入顺序相同
const charFadeOutTime = Math.max(0, cycleTime - fadeOutStart - charFadeOutDelay);
charOpacitiesRef.current[i] = Math.max(0, 1 - charFadeOutTime / 300);
}
}
}, layerRef.current);
anim.start();
return () => {
anim.stop();
};
}, []);
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer ref={layerRef}>
<Text
ref={textRef}
x={window.innerWidth / 2 - 100}
y={window.innerHeight / 2 - 20}
text="ANIMATION"
fontSize={40}
fontFamily="Arial"
fill="#333"
charRenderFunc={({ context, index }) => {
context.globalAlpha = charOpacitiesRef.current[index] || 0;
}}
/>
</Layer>
</Stage>
);
};
export default App;
<template>
<v-stage :config="stageSize">
<v-layer ref="layerRef">
<v-text
ref="textRef"
:config="textConfig"
/>
</v-layer>
</v-stage>
</template>
<script setup>
import { ref, onMounted, onUnmounted, reactive } from 'vue';
import Konva from 'konva';
const stageSize = {
width: window.innerWidth,
height: window.innerHeight
};
const textConfig = reactive({
x: window.innerWidth / 2 - 100,
y: window.innerHeight / 2 - 20,
text: 'ANIMATION',
fontSize: 40,
fontFamily: 'Arial',
fill: '#333',
charRenderFunc: ({ context, index }) => {
context.globalAlpha = charOpacities[index] || 0;
}
});
const charOpacities = reactive([]);
const layerRef = ref(null);
const textRef = ref(null);
let anim = null;
onMounted(() => {
anim = new Konva.Animation((frame) => {
const time = frame.time;
const cycleDuration = 4000; // 4 秒总周期
const fadeInDuration = 1500; // 1.5 秒全部淡入
const holdDuration = 1000; // 1 秒保持
const fadeOutDuration = 1500; // 1.5 秒全部淡出
const cycleTime = time % cycleDuration;
for (let i = 0; i < textRef.value.getNode().text().length; i++) {
const charDelay = i * 150; // 字符之间延迟 150 毫秒
if (cycleTime < fadeInDuration) {
// 淡入阶段
const charStartTime = charDelay;
const charFadeTime = Math.max(0, cycleTime - charStartTime);
charOpacities[i] = Math.min(1, charFadeTime / 300);
} else if (cycleTime < fadeInDuration + holdDuration) {
// 保持阶段 - 所有字符可见
charOpacities[i] = 1;
} else {
// 淡出阶段
const fadeOutStart = fadeInDuration + holdDuration;
const charFadeOutDelay = i * 150; // 与淡入顺序相同
const charFadeOutTime = Math.max(0, cycleTime - fadeOutStart - charFadeOutDelay);
charOpacities[i] = Math.max(0, 1 - charFadeOutTime / 300);
}
}
}, layerRef.value.getNode());
anim.start();
});
onUnmounted(() => {
if (anim) {
anim.stop();
}
});
</script>