Vue动态创建组件实例并挂载到body
方式一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import Vue from 'vue' /** * @param Component 组件实例的选项对象 * @param props 组件实例中的prop */ export function create(Component, props) { const comp = new (Vue.extend(Component))({ propsData: props }).$mount() document.body.appendChild(comp.$el) comp.remove = () => { document.body.removeChild(comp.$el) comp.$destroy() } return comp } |
方式二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import Vue from 'vue' export function create(Component, props) { // 借鸡生蛋new Vue({render() {}}),在render中把Component作为根组件 const vm = new Vue({ // h是createElement函数,它可以返回虚拟dom render(h) { console.log(h(Component,{ props })); // 将Component作为根组件渲染出来 // h(标签名称或组件配置对象,传递属性、事件等,孩子元素) return h(Component, { props }) } }).$mount() // 挂载是为了把虚拟dom变成真实dom // 不挂载就没有真实dom // 手动追加至body // 挂载之后$el可以访问到真实dom document.body.appendChild(vm.$el) console.log(vm.$children); // 实例 const comp = vm.$children[0] // 淘汰机制 comp.remove = () => { // 删除dom document.body.removeChild(vm.$el) // 销毁组件 vm.$destroy() } // 返回Component组件实例 return comp } |
使用
- A组件(要动态创建的组件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | <template> <div class = "a" > <h2>{{ title }}</h2> <p>{{ data }}</p> </div> </template> <script> export default { props: { title: { type: String, default : "hello world!" }, message: { type: String, default : "o(∩_∩)o 哈哈" }, duration: { type: Number, default : 1000 } }, data() { return { data: "我是a组件" , }; }, created() { let num = 1 const timer = setInterval(() => { this .data = num++ }, this .duration) this .$once( "hook: beforeDestroy" , () => clearInterval(timer)) } }; </script> <style> .a { position: fixed; width: 100%; top: 16px; left: 0; text-align: center; pointer-events: none; background-color: #fff; border: grey 3px solid; box-sizing: border-box; } </style> |
- B组件(操作动态创建组件的地方)
<template> <div class="b"> <button @click="createA">创建</button> </div> </template> <script> import A from "@/components/A.vue" import { create } from "@/utils/create.js" export default { components: { A, }, methods: { createA() { // 创建A组件,并挂载到body上 create(A, { title: "vue", message: "么么哒😙" }) // 可以实现功能。,没问题,但是每次引入那么多组件,方法很不方便,下面改写成用this.$comp.来调用 } }, }; </script>
图片点击放大组件改写:
需求: 组件需要挂载到body节点上,因为弹窗里面再弹窗会导致遮罩层很小的问题,这里就需要重新挂载到body上
imgBig.vue 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | <template> <div v- if = "imgBigVisible1" tabindex= "-1" class = "img-viewer__wrapper" style= "z-index: 9999" > <!--v- if = "imgBigVisible1" --> <div class = "img-viewer__mask" ></div> <span class = "img-viewer__btn img-viewer__close" @click= "close" > <i class = "el-icon-circle-close" ></i> </span> <div class = "img-viewer__btn img-viewer__actions" > <div class = "img-viewer__actions__inner" > <i class = "el-icon-zoom-out" @click= "zoomOut" ></i> <i class = "el-icon-zoom-in" @click= "zoomIn" ></i> <i class = "img-viewer__actions__divider" ></i> <i : class = "[original?'el-icon-full-screen':'el-icon-c-scale-to-original']" @click= "zoom" ></i> <i class = "img-viewer__actions__divider" ></i> <i class = "el-icon-refresh-left" @click= "rotateLeft" ></i> <i class = "el-icon-refresh-right" @click= "rotateRight" ></i> </div> </div> <div class = "img-viewer__canvas" > <img :src= "srcURL" class = "img-viewer__img" :style= "[{transform: `scale(${scale}) rotate(${deger}deg)`}, original?maxStyle:'']" /> </div> </div> </template> <script> export default { props: { // imgBigVisible: { // type: Boolean, // default: false // }, srcURL: { type: String, default : '' } }, data() { return { imgBigVisible1: false , scale: 1, deger: 0, original: false , maxStyle: { maxHeight: '100%' , maxWidth: '100%' } }; }, computed: { }, watch: { // imgBigVisible (v, ov) { // console.log('props imgBigVisible', v) // this.imgBigVisible1 = v // }, srcURL (v, ov) { // props传值 console.log( 'src====' , typeof v) } }, methods: { show () { this .imgBigVisible1 = true }, close () { this .imgBigVisible1 = false this .$emit( 'update:imgBigVisible' , this .imgBigVisible1) }, zoom () { this .original = ! this .original this .scale = 1 this .deger = 0 }, zoomOut () { this .scale -= 0.2 if ( this .scale < 0.2) { this .scale = 0.2 return } }, zoomIn () { this .scale += 0.2 }, rotateLeft () { this .deger -= 90 }, rotateRight () { this .deger += 90 } }, }; </script> <style lang= "scss" scoped> .img-viewer__wrapper { position: fixed; top: 0; right: 0; bottom: 0; left: 0; .img-viewer__mask { position: absolute; width: 100%; height: 100%; top: 0; left: 0; opacity: .5; background: #000; } .img-viewer__btn { position: absolute; z-index: 1; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; border-radius: 50%; opacity: .8; cursor: pointer; box-sizing: border-box; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .img-viewer__close { top: 40px; right: 40px; width: 40px; height: 40px; font-size: 40px; color: #c7c7c7; } .img-viewer__actions { left: 50%; bottom: 30px; transform: translateX(-50%); width: 282px; height: 44px; padding: 0 23px; background-color: #606266; border-color: #fff; border-radius: 22px; .img-viewer__actions__inner { width: 100%; height: 100%; text-align: justify; cursor: default ; font-size: 23px; color: #fff; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: distribute; justify-content: space-around; } } .img-viewer__canvas { width: 100%; height: 100%; display: -ms-flexbox; display: flex; -ms-flex-pack: center; justify-content: center; -ms-flex-align: center; align-items: center; } .img-viewer__img { margin-left: 0px; margin-top: 0px; transition: transform 0.3s ease 0s; } } </style> |
index.js
import Vue from 'vue' import imgBig from './imgBig' let imgBigConstructor = Vue.extend(imgBig) const myImgBig = (props) => { const instance = new imgBigConstructor({ // 生成实例 propsData: props // 注意这里propsData不要写错了 }) instance.$mount() // 实例挂载 document.body.appendChild(instance.$el);//3原生方法插入body return instance } export default myImgBig
main.js 引入
import imgBig from '@/temp/imgBig/index.js'
Vue.prototype.$imgBig = imgBig
父组件调用
1 2 3 4 5 6 7 8 9 10 | <span v- if = "isImgFile(item.name)" style= "cursor:pointer;" @click= "viewImgBig(item.url)" >{{item.name}}</span> ... isImgFile(value){ return value.endWith( '.jpg' ) || value.endWith( '.jpeg' ) || value.endWith( '.png' ); }, viewImgBig (src) { this .$imgBig({srcURL: src}).show() }, |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗