Text Animations Tutorial
Note: this feature is only available from Konva v10.0.0.
Konva provides powerful text animation capabilities through the charRenderFunc property. This function allows you to customize how each character is rendered, enabling character-by-character animations and effects.
var text = new Konva.Text({
x: 10,
y: 10,
text: 'AB',
fontSize: 20,
charRenderFunc: function ({ context, index }) {
if (index === 1) {
// shift only the second character
context.translate(0, 10);
}
},
});
The charRenderFunc receives a context object with the following parameters:
char- The actual character string being renderedindex- Zero-based index of the character in the entire textx- X position where the character will be renderedy- Y position where the character will be renderedlineIndex- Zero-based index of the line containing this charactercolumn- Zero-based column position within the current lineisLastInLine- Boolean indicating if this is the last character in its linewidth- Width of the charactercontext- Canvas 2D rendering context for applying transformations, opacity, colors, etc.
This allows you to apply transformations, opacity changes, or other effects to individual characters based on their position and properties.
- 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);
// we will store the opacity of each character in an array
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 seconds total cycle
const fadeInDuration = 1500; // 1.5 seconds to fade in all
const holdDuration = 1000; // 1 second hold
const fadeOutDuration = 1500; // 1.5 seconds to fade out all
const cycleTime = time % cycleDuration;
for (let i = 0; i < textNode.text().length; i++) {
const charDelay = i * 150; // 150ms delay between characters
if (cycleTime < fadeInDuration) {
// Fade in phase
const charStartTime = charDelay;
const charFadeTime = Math.max(0, cycleTime - charStartTime);
charOpacities[i] = Math.min(1, charFadeTime / 300);
} else if (cycleTime < fadeInDuration + holdDuration) {
// Hold phase - all characters visible
charOpacities[i] = 1;
} else {
// Fade out phase
const fadeOutStart = fadeInDuration + holdDuration;
const charFadeOutDelay = i * 150; // Same order as fade in
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 seconds total cycle
const fadeInDuration = 1500; // 1.5 seconds to fade in all
const holdDuration = 1000; // 1 second hold
const fadeOutDuration = 1500; // 1.5 seconds to fade out all
const cycleTime = time % cycleDuration;
for (let i = 0; i < textRef.current.text().length; i++) {
const charDelay = i * 150; // 150ms delay between characters
if (cycleTime < fadeInDuration) {
// Fade in phase
const charStartTime = charDelay;
const charFadeTime = Math.max(0, cycleTime - charStartTime);
charOpacitiesRef.current[i] = Math.min(1, charFadeTime / 300);
} else if (cycleTime < fadeInDuration + holdDuration) {
// Hold phase - all characters visible
charOpacitiesRef.current[i] = 1;
} else {
// Fade out phase
const fadeOutStart = fadeInDuration + holdDuration;
const charFadeOutDelay = i * 150; // Same order as fade in
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 seconds total cycle
const fadeInDuration = 1500; // 1.5 seconds to fade in all
const holdDuration = 1000; // 1 second hold
const fadeOutDuration = 1500; // 1.5 seconds to fade out all
const cycleTime = time % cycleDuration;
for (let i = 0; i < textRef.value.getNode().text().length; i++) {
const charDelay = i * 150; // 150ms delay between characters
if (cycleTime < fadeInDuration) {
// Fade in phase
const charStartTime = charDelay;
const charFadeTime = Math.max(0, cycleTime - charStartTime);
charOpacities[i] = Math.min(1, charFadeTime / 300);
} else if (cycleTime < fadeInDuration + holdDuration) {
// Hold phase - all characters visible
charOpacities[i] = 1;
} else {
// Fade out phase
const fadeOutStart = fadeInDuration + holdDuration;
const charFadeOutDelay = i * 150; // Same order as fade in
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>