Fabric.js在Vue3组件内部canvas上无法渲染对象问题求助
问题根因
- 你的canvas放在HeadlessUI的过渡组件内,
showCanvas变为true时仅代表过渡开始,此时canvas元素还未完成真实DOM挂载,Fabric初始化时找不到对应DOM节点导致渲染失效 - 直接用id选择器获取动态渲染的DOM不稳定,Vue场景下优先使用ref绑定DOM
- canvas未显式设置宽高,默认尺寸极小,即使绘制成功也无法观测
- 多余的
reactive(props.showCanvas)调用无任何作用,props本身自带响应性,不需要额外包裹
解决方案
模板修改点
- 给
TransitionRoot添加@after-enter事件,入场动画完全结束、DOM挂载完成后再初始化Fabric - 给canvas绑定ref替代id选择器,同时设置固定宽高
- 给
TransitionRoot添加@after-leave事件,弹窗关闭时销毁Fabric实例避免内存泄漏
完整修改后代码
<template> <TransitionRoot as="template" :show="showCanvas" @after-enter="initCanvas" @after-leave="destroyCanvas"> <Dialog as="div" class="fixed z-10 inset-0" @close="showCanvas = false"> <div class="w-full" style="width: 60%!important; height:auto"> <TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0"> <DialogOverlay class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> </TransitionChild> <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> <TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leave-from="opacity-100 translate-y-0 sm:scale-100" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"> <div class="bg-white"> <div> <div class="mt-3 text-center sm:mt-5"> <div class="fixed inset-0 overflow-y-auto z-50 flex justify-center items-center"> <div class=" bg-white m-auto rounded-lg shadow-md" role="dialog" aria-modal="true" aria-labelledby="modal- headline" style="width: 550px !important;height:auto;"> <!-- 绑定ref + 设置固定宽高 --> <canvas ref="canvasRef" width="500" height="400"></canvas> </div> </div> <div class="flex w-full mx-auto absolute bottom-56 justify-center z-50"> <button type="button" class="w-50 mr-2 inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:col-start-2 sm:text-sm" > Save </button> <button type="button" class="mt-3 w-50 ml-2 inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:col-start-1 sm:text-sm" @click="closePopup" ref="cancelButtonRef"> Cancel </button> </div> </div> </div> </div> </TransitionChild> </div> </Dialog> </TransitionRoot> </template> <script> import { fabric } from 'fabric' import { ref, nextTick } from 'vue'; import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue' export default { components: { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot, }, props: { showCanvas: { required: true, type: Boolean }, }, setup(props, context) { const canvasRef = ref(null) let canvas = null function initCanvas() { // 确保DOM存在再初始化 if (!canvasRef.value) return canvas = new fabric.Canvas(canvasRef.value) const rectangle = new fabric.Rect({ width: 300, height: 200, fill: '', stroke: 'green', strokeWidth: 5 }); canvas.add(rectangle); } function destroyCanvas() { // 关闭弹窗时销毁实例避免内存泄漏 if (canvas) { canvas.dispose() canvas = null } } function closePopup() { context.emit("closeCanvas") } // 若不需要等待过渡结束再渲染,可保留watch逻辑配合nextTick使用 // watch(() => props.showCanvas, (newVal) => { // if (newVal) nextTick(() => initCanvas()) // }) return { canvasRef, closePopup, initCanvas, destroyCanvas } } } </script>
内容的提问来源于stack exchange,提问作者Anirudh
相关产品推荐
相关产品推荐

