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()
},

  

 

 

    

posted @   front-gl  阅读(2761)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示