Vue项目中Canvas适配Video标签显示比例问题问询
问题描述
我开发了一个用户注册需拍摄照片的Vue项目,客户要求video标签高度设为屏幕100%。video标签上方叠加了滤镜图片,因此video宽度与滤镜一致、高度为屏幕高度,且设置了object-fit:cover实现铺满显示。现在需要调整Canvas,让生成的图片和video的显示尺寸完全一致,但video宽度随滤镜动态变化,无法获取其实际宽度,求解决方案。
Video组件
<template> <div class="takePhoto__camera"> <video ref="video" fullscreen="false" playsinline muted></video> <img class="takePhoto__camera__filter" src="../../assets/images/filtro.png" ref="filter" alt="Filtro"> <button class="takePhoto__camera__btn" @click.prevent="photoTaken"></button> </div> </template> <script> import { mapMutations } from "vuex" export default { name: "ComponentCameraVideo", data(){ return{ video: null, localStream: null, } }, mounted(){ this.video = this.$refs.video; this.init(); }, methods: { ...mapMutations(["SET_CAMERA"]), init(){ const filter = document.querySelector(".takePhoto__camera__filter"); // 给body添加类名 document.body.classList.add("takingPicture"); filter.onload = () => { // 请求摄像头权限 if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices){ navigator.mediaDevices.getUserMedia({video: true}).then((stream) => { this.video.srcObject = stream; this.localStream = stream; this.video.play(); this.setWidthVideo(); }).catch((error) => { console.log(error) alert(error); }) } else{ alert("无法访问摄像头!"); } } }, photoTaken(){ // 移除body类名 document.body.classList.remove("takingPicture"); // 将摄像头数据存入Vuex this.SET_CAMERA( { "video" : { element: this.video, width: this.video.offsetWidth, height: this.video.offsetHeight }, "filter" : { element: this.$refs.filter, width: this.$refs.filter.offsetWidth, height: this.$refs.filter.offsetHeight }, "stream" : this.localStream, } ); // 触发拍照完成事件 this.$emit("photoTaken"); // 停止摄像头(注释状态) // this.localStream.getTracks()[0].stop(); }, setWidthVideo(){ setTimeout(() => { // 根据滤镜宽度设置video宽度 const video = document.querySelector(".takePhoto__camera video"); const filter = document.querySelector(".takePhoto__camera__filter"); video.style.width = filter.offsetWidth + "px"; video.style.height = innerHeight + "px"; }, 500) } }, } </script>
Canvas组件
<template> <div class="takePhoto__showPhoto"> <canvas ref="canvas"></canvas> <img :src="src" alt="你的照片"> </div> </template> <script> import { mapState, mapMutations } from "vuex" export default { name: "ComponentCameraCanvas", data() { return { canvas: null, src: "/", }; }, computed: { ...mapState(["camera"]) }, mounted() { this.canvas = this.$refs.canvas; this.getPhoto() }, methods: { ...mapMutations(["SET_PHOTO"]), getPhoto(){ const ratio = (window.innerHeight < window.innerWidth) ? 16 / 9 : 9 / 16; const ctx = this.canvas.getContext("2d"); // 设置Canvas尺寸 this.canvas.width = this.camera.filter.width; this.canvas.height = this.camera.filter.height; // Canvas配置 ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = "high"; // 加载滤镜图片 let filter = new Image(); filter.src = this.camera.filter.element.src // 在Canvas中绘制画面和滤镜 ctx.drawImage(this.camera.video.element, 0, 0); ctx.drawImage(filter, 0, 0, this.camera.filter.width, this.camera.filter.height); // 停止摄像头 this.camera.stream.getTracks()[0].stop() // 将照片存入Vuex this.SET_PHOTO(this.canvas.toDataURL("image/jpeg")); // 在img标签中展示照片 this.src = this.canvas.toDataURL("image/jpeg"); }, }, } </script> <style lang="scss" scoped> #picture { display: block; width: 100%; height: 100vh; box-sizing: border-box; canvas { display: block; width: 100%; height: 100%; } } </style>
解决方案
1. 准确获取video的实际渲染尺寸
替换offsetWidth为getBoundingClientRect(),该方法能获取元素实时渲染的宽高,适配动态变化场景。修改Video组件的photoTaken方法:
photoTaken(){ document.body.classList.remove("takingPicture"); // 获取元素真实渲染尺寸 const videoRect = this.video.getBoundingClientRect(); const filterRect = this.$refs.filter.getBoundingClientRect(); this.SET_CAMERA( { "video" : { element: this.video, width: videoRect.width, height: videoRect.height, // 保存视频原始分辨率,用于计算裁剪比例 naturalWidth: this.video.videoWidth, naturalHeight: this.video.videoHeight }, "filter" : { element: this.$refs.filter, width: filterRect.width, height: filterRect.height }, "stream" : this.localStream, } ); this.$emit("photoTaken"); }
2. 移除不稳定的定时器依赖
把setTimeout替换为requestAnimationFrame,确保DOM更新后再设置video尺寸,避免延迟误差:
setWidthVideo(){ requestAnimationFrame(() => { const filter = this.$refs.filter; this.video.style.width = `${filter.offsetWidth}px`; this.video.style.height = `${window.innerHeight}px`; }); }
3. 匹配video的object-fit:cover绘制效果
直接绘制视频会导致拉伸,需计算裁剪区域,让Canvas画面和video显示完全一致。修改Canvas组件的getPhoto方法:
getPhoto(){ const ctx = this.canvas.getContext("2d"); const { video, filter } = this.camera; // 同步Canvas尺寸与滤镜(即video显示尺寸) this.canvas.width = filter.width; this.canvas.height = filter.height; ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = "high"; // 计算裁剪参数,实现object-fit:cover效果 const videoRatio = video.naturalWidth / video.naturalHeight; const displayRatio = video.width / video.height; let drawWidth, drawHeight, offsetX, offsetY; if (videoRatio > displayRatio) { // 视频宽于显示区域,裁剪左右 drawHeight = video.naturalHeight; drawWidth = displayRatio * drawHeight; offsetX = (video.naturalWidth - drawWidth) / 2; offsetY = 0; } else { // 视频高于显示区域,裁剪上下 drawWidth = video.naturalWidth; drawHeight = drawWidth / displayRatio; offsetY = (video.naturalHeight - drawHeight) / 2; offsetX = 0; } // 绘制裁剪后的视频画面 ctx.drawImage( video.element, offsetX, offsetY, drawWidth, drawHeight, // 视频裁剪区域 0, 0, filter.width, filter.height // 绘制到Canvas的区域 ); // 绘制滤镜(需等待图片加载完成) let filterImg = new Image(); filterImg.src = filter.element.src; filterImg.onload = () => { ctx.drawImage(filterImg, 0, 0, filter.width, filter.height); // 保存图片到Vuex并展示 this.SET_PHOTO(this.canvas.toDataURL("image/jpeg")); this.src = this.canvas.toDataURL("image/jpeg"); // 停止摄像头 this.camera.stream.getTracks()[0].stop(); }; }
4. 补充CSS确保video适配生效
在Video组件的<style>中添加:
.takePhoto__camera video { object-fit: cover; display: block; }
内容的提问来源于stack exchange,提问作者Gabriel Edu
相关产品推荐
相关产品推荐

