如何在画布上显示视频
要在画布上绘制视频,我们可以使用 <video>
DOM 元素,类似于 <img>
元素,但我们必须频繁地重绘图层。为此,我们可以使用 Konva.Animation
。作为替代方案,您可以使用 requestAnimationFrame
并仅调用 layer.draw()
。
另外,请查看这篇文章以获取更多信息: 案例研究: 流媒体视频编辑器
下面的演示展示了如何在画布上使用播放/暂停控件显示视频。您还可以拖放视频。
- Vanilla
- React
- Vue
import Konva from 'konva'; // 创建按钮 const playButton = document.createElement('button'); playButton.textContent = '播放'; playButton.id = 'play'; document.body.appendChild(playButton); const pauseButton = document.createElement('button'); pauseButton.textContent = '暂停'; 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'); video.src = 'https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c4/Physicsworks.ogv/Physicsworks.ogv.240p.vp9.webm'; const image = new Konva.Image({ image: video, draggable: true, x: 50, y: 20, }); layer.add(image); const text = new Konva.Text({ text: '正在加载视频...', width: stage.width(), height: stage.height(), align: 'center', verticalAlign: 'middle', }); layer.add(text); const anim = new Konva.Animation(function () { // 什么也不做,动画只是需要更新图层 }, layer); // 在元数据加载时更新 Konva.Image 大小 video.addEventListener('loadedmetadata', function () { text.text('按下播放...'); image.width(video.videoWidth); image.height(video.videoHeight); }); document.getElementById('play').addEventListener('click', function () { text.destroy(); video.play(); anim.start(); }); document.getElementById('pause').addEventListener('click', function () { video.pause(); anim.stop(); });
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(() => { const element = document.createElement('video'); element.src = 'https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c4/Physicsworks.ogv/Physicsworks.ogv.240p.vp9.webm'; return element; }); const [videoSize, setVideoSize] = useState({ width: 0, height: 0 }); const [status, setStatus] = useState('正在加载视频...'); 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('按下播放...'); setVideoSize({ width: videoElement.videoWidth, height: videoElement.videoHeight, }); }; videoElement.addEventListener('loadedmetadata', handleMetadata); return () => videoElement.removeEventListener('loadedmetadata', handleMetadata); }, [videoElement]); const handlePlay = () => { setStatus(''); videoElement.play(); if (layerRef.current) { const anim = new Konva.Animation(() => {}, layerRef.current); animationRef.current = anim; anim.start(); } }; const handlePause = () => { videoElement.pause(); if (animationRef.current) { animationRef.current.stop(); } }; return ( <div> <button onClick={handlePlay}>播放</button> <button onClick={handlePause}>暂停</button> <Stage width={dimensions.width} height={dimensions.height}> <Layer ref={layerRef}> <Image image={videoElement} x={50} y={20} width={videoSize.width} height={videoSize.height} draggable /> {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">播放</button> <button @click="handlePause">暂停</button> <v-stage :config="stageConfig"> <v-layer ref="layerRef"> <v-image :config="{ image: videoElement, x: 50, y: 20, width: videoSize.width, height: videoSize.height, draggable: true, }" /> <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 { ref, onMounted, onUnmounted } from 'vue'; export default { setup() { const width = ref(window.innerWidth); const height = ref(400); const layerRef = ref(null); const status = ref('正在加载视频...'); const animation = ref(null); const videoElement = ref(document.createElement('video')); const videoSize = ref({ width: 0, height: 0 }); videoElement.value.src = 'https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c4/Physicsworks.ogv/Physicsworks.ogv.240p.vp9.webm'; const stageConfig = { width: width.value, height: height.value, }; const handleResize = () => { width.value = window.innerWidth; }; const handleMetadata = () => { status.value = '按下播放...'; videoSize.value = { width: videoElement.value.videoWidth, height: videoElement.value.videoHeight, }; }; const handlePlay = () => { status.value = ''; videoElement.value.play(); if (layerRef.value) { const anim = new Konva.Animation(() => {}, layerRef.value.getNode()); animation.value = anim; anim.start(); } }; const handlePause = () => { videoElement.value.pause(); if (animation.value) { animation.value.stop(); } }; onMounted(() => { window.addEventListener('resize', handleResize); videoElement.value.addEventListener('loadedmetadata', handleMetadata); }); onUnmounted(() => { window.removeEventListener('resize', handleResize); videoElement.value.removeEventListener('loadedmetadata', handleMetadata); if (animation.value) { animation.value.stop(); } }); return { stageConfig, layerRef, status, videoElement, videoSize, handlePlay, handlePause, }; }, }; </script>
演示展示了如何:
- 创建一个视频元 素并将其用作 Konva.Image 的源
- 为视频实现播放/暂停控制
- 使用 Konva.Animation 在视频播放时连续更新图层
- 在画布上使视频可拖动
- 显示加载和播放状态信息
- 处理视频元数据以设置正确的尺寸
尝试播放视频并将其拖动到画布上。视频会在您移动时继续播放。