How to display video on Canvas
To draw a video on a canvas, we can use a <video> DOM element similar to an <img> element, but we have to frequently redraw the layer. For this purpose, we can use Konva.Animation. As an alternative, you can use requestAnimationFrame and just call layer.draw().
Also take a look at this post for additional information: Case Study: Video Editor for Stream
The demo below shows how to display a video on canvas with play/pause controls. You can also drag and drop the video around the canvas.
- Vanilla
- React
- Vue
import Konva from 'konva';
// create buttons
const playButton = document.createElement('button');
playButton.textContent = 'Play';
playButton.id = 'play';
document.body.appendChild(playButton);
const pauseButton = document.createElement('button');
pauseButton.textContent = 'Pause';
pauseButton.id = 'pause';
document.body.appendChild(pauseButton);
const width = window.innerWidth;
const height = 300;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
const video = document.createElement('video');
const image = new Konva.Image({
image: video,
draggable: true,
x: 50,
y: 20,
});
layer.add(image);
const text = new Konva.Text({
text: 'Loading video...',
width: stage.width(),
height: stage.height(),
align: 'center',
verticalAlign: 'middle',
});
layer.add(text);
const anim = new Konva.Animation(function () {
// do nothing, animation just needs to update the layer
}, layer);
// update Konva.Image size when meta is loaded
video.addEventListener('loadedmetadata', function () {
text.text('Press PLAY...');
image.width(video.videoWidth);
image.height(video.videoHeight);
});
video.src =
'https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c4/Physicsworks.ogv/Physicsworks.ogv.240p.vp9.webm';
document.getElementById('play').addEventListener('click', function () {
text.destroy();
video.play();
anim.start();
});
document.getElementById('pause').addEventListener('click', function () {
video.pause();
anim.stop();
});
import Konva from 'konva';
import { Stage, Layer, Image, Text } from 'react-konva';
import { useEffect, useRef, useState } from 'react';
const App = () => {
const [dimensions, setDimensions] = useState({
width: window.innerWidth,
height: 400,
});
const [videoElement] = useState(() => document.createElement('video'));
const [videoSize, setVideoSize] = useState({ width: 0, height: 0 });
const [videoPosition, setVideoPosition] = useState({ x: 50, y: 20 });
const [status, setStatus] = useState('Loading video...');
const animationRef = useRef(null);
const layerRef = useRef(null);
useEffect(() => {
const handleResize = () => {
setDimensions({
width: window.innerWidth,
height: 400,
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
useEffect(() => {
const handleMetadata = () => {
setStatus('Press PLAY...');
setVideoSize({
width: videoElement.videoWidth,
height: videoElement.videoHeight,
});
};
videoElement.addEventListener('loadedmetadata', handleMetadata);
videoElement.src =
'https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c4/Physicsworks.ogv/Physicsworks.ogv.240p.vp9.webm';
if (videoElement.readyState >= 1) handleMetadata();
return () => videoElement.removeEventListener('loadedmetadata', handleMetadata);
}, [videoElement]);
useEffect(() => {
if (!layerRef.current) return;
const animation = new Konva.Animation(() => {}, layerRef.current);
animationRef.current = animation;
return () => {
animation.stop();
videoElement.pause();
animationRef.current = null;
};
}, [videoElement]);
const handlePlay = () => {
setStatus('');
videoElement.play();
animationRef.current?.start();
};
const handlePause = () => {
videoElement.pause();
if (animationRef.current) {
animationRef.current.stop();
}
};
return (
<div>
<button onClick={handlePlay}>Play</button>
<button onClick={handlePause}>Pause</button>
<Stage width={dimensions.width} height={dimensions.height}>
<Layer ref={layerRef}>
<Image
image={videoElement}
x={videoPosition.x}
y={videoPosition.y}
width={videoSize.width}
height={videoSize.height}
draggable
onDragEnd={(event) => setVideoPosition(event.target.position())}
/>
{status && (
<Text
text={status}
width={dimensions.width}
height={dimensions.height}
align="center"
verticalAlign="middle"
/>
)}
</Layer>
</Stage>
</div>
);
};
export default App;
<template>
<div>
<button @click="handlePlay">Play</button>
<button @click="handlePause">Pause</button>
<v-stage :config="stageConfig">
<v-layer ref="layerRef">
<v-image
:config="{
image: videoElement,
x: videoPosition.x,
y: videoPosition.y,
width: videoSize.width,
height: videoSize.height,
draggable: true,
}"
@dragend="videoPosition = $event.target.position()"
/>
<v-text
v-if="status"
:config="{
text: status,
width: stageConfig.width,
height: stageConfig.height,
align: 'center',
verticalAlign: 'middle',
}"
/>
</v-layer>
</v-stage>
</div>
</template>
<script>
import Konva from 'konva';
import { computed, ref, onMounted, onUnmounted } from 'vue';
export default {
setup() {
const width = ref(window.innerWidth);
const height = ref(400);
const layerRef = ref(null);
const status = ref('Loading video...');
let animation = null;
const videoElement = ref(document.createElement('video'));
const videoSize = ref({ width: 0, height: 0 });
const videoPosition = ref({ x: 50, y: 20 });
const stageConfig = computed(() => ({
width: width.value,
height: height.value,
}));
const handleResize = () => {
width.value = window.innerWidth;
};
const handleMetadata = () => {
status.value = 'Press PLAY...';
videoSize.value = {
width: videoElement.value.videoWidth,
height: videoElement.value.videoHeight,
};
};
const handlePlay = () => {
status.value = '';
videoElement.value.play();
animation?.start();
};
const handlePause = () => {
videoElement.value.pause();
animation?.stop();
};
onMounted(() => {
window.addEventListener('resize', handleResize);
videoElement.value.addEventListener('loadedmetadata', handleMetadata);
videoElement.value.src =
'https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c4/Physicsworks.ogv/Physicsworks.ogv.240p.vp9.webm';
if (videoElement.value.readyState >= 1) handleMetadata();
animation = new Konva.Animation(() => {}, layerRef.value.getNode());
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
videoElement.value.removeEventListener('loadedmetadata', handleMetadata);
animation?.stop();
videoElement.value.pause();
});
return {
stageConfig,
layerRef,
status,
videoElement,
videoSize,
videoPosition,
handlePlay,
handlePause,
};
},
};
</script>
The demo shows how to:
- Create a video element and use it as the source for a Konva.Image
- Implement play/pause controls for the video
- Use Konva.Animation to continuously update the layer while the video is playing
- Make the video draggable on the canvas
- Display loading and play status messages
- Handle video metadata to set the correct dimensions
Try playing the video and dragging it around the canvas. The video will continue playing while you move it.