You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Fabric.js在Vue3组件内部canvas上无法渲染对象问题求助

问题根因

  • 你的canvas放在HeadlessUI的过渡组件内,showCanvas变为true时仅代表过渡开始,此时canvas元素还未完成真实DOM挂载,Fabric初始化时找不到对应DOM节点导致渲染失效
  • 直接用id选择器获取动态渲染的DOM不稳定,Vue场景下优先使用ref绑定DOM
  • canvas未显式设置宽高,默认尺寸极小,即使绘制成功也无法观测
  • 多余的reactive(props.showCanvas)调用无任何作用,props本身自带响应性,不需要额外包裹

解决方案

模板修改点

  1. 给TransitionRoot添加@after-enter事件,入场动画完全结束、DOM挂载完成后再初始化Fabric
  2. 给canvas绑定ref替代id选择器,同时设置固定宽高
  3. 给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">&#8203;</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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 00:45:00