效果
样式效果
未点击时候是这样的 点击按钮,弹出弹框,弹框是主动添加到body下的标签 拉动浏览器窗口,弹框可以自动调节显示位置
代码效果:
代码调用和 el-popover 保持一致。
- 使用具名插槽reference,来显示触发按钮
- 使用默认插槽来显示弹框
<template>
<div class="clips">
<lm-popover popper-class="customClass" trigger="click">
<div class="content">
我是要显示的内容
</div>
<template v-slot:reference>
<el-button>成功按钮</el-button>
</template>
</lm-popover>
</div>
</template>
<script>
import lmPopover from './lm-popover.vue'
export default {
components: {
lmPopover
}
}
</script>
<style>
.customClass.content{
}
</style>
源码与解析
解析
- 如何创建组件,并且绑定到body中
看一段入口文件main.js的代码: 是不是特别的熟悉呢? 实例化vue,render的实际上是渲染App.vue组件。并绑定到#app上,那么做一个改造吧,将其绑定到自己生成的div中:
this.dom = new Vue({
el: document.createElement('div'),
render: (createElement) => {
},
})
document.body.appendChild(this.dom.$el);
el相当于上图的
m
o
u
n
t
,
加
真
实
D
O
M
绑
定
到
手
动
创
建
的
d
i
v
中
,
在
调
用
组
件
中
申
明
一
个
变
量
d
o
m
等
于
实
例
化
的
v
u
e
,
就
能
得
到
这
个
v
u
e
的
实
例
化
对
象
了
。
就
是
能
用
到
t
h
i
s
中
的
数
值
了
。
再
通
过
将
mount,加真实DOM绑定到手动创建的div中, 在调用组件中申明一个变量dom等于实例化的vue,就能得到这个vue的实例化对象了。就是能用到this中的数值了。 再通过将
mount,加真实DOM绑定到手动创建的div中,在调用组件中申明一个变量dom等于实例化的vue,就能得到这个vue的实例化对象了。就是能用到this中的数值了。再通过将el将标签绑定到body上。就能渲染页面了。 createElement 如何使用呢?如下,也可以看下vue官网介绍 createElement 使用规则
createElement(
'div',
{
},
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
{
'class': {
foo: true,
bar: false
},
style: {
color: 'red',
fontSize: '14px'
},
attrs: {
id: 'foo'
},
props: {
myProp: 'bar'
},
domProps: {
innerHTML: 'baz'
},
on: {
click: this.clickHandler
},
nativeOn: {
click: this.nativeClickHandler
},
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
scopedSlots: {
default: props => createElement('span', props.text)
},
slot: 'name-of-slot',
key: 'myKey',
ref: 'myRef',
refInFor: true
}
因此我们贴上我的源码来分析: 第一个参数 :popover 使我们手动写的popover.vue文件组件,相当于一个壳子, 导入后放在这里。 createElement实际上就是依靠这个popover壳子组件来执行,给出props等传参的值,最终生成虚拟DOM。this.$slots.default是当前将外部调用的默认插槽放到popover中的子元素插槽。
new Vue({
el: document.createElement('div'),
render: (createElement) => {
return createElement(popover, {
}, this.$slots.default)
},
})
源码
<template>
<div
class="insertDom"
@click="clickTrigger"
>
<slot name="reference"></slot>
</div>
</template>
<script>
import Vue from 'vue'
import popover from './popover.vue'
export default {
data () {
return {
dom: null
}
},
props: {
popperClass: {
type: String
},
closeBtn: {
type: Boolean,
default: false
}
},
methods: {
create (e) {
this.dom = new Vue({
el: document.createElement('div'),
render: (createElement) => {
return createElement(popover, {
props: {
popperClass: this.$props.popperClass,
closeBtn: this.$props.closeBtn,
hide: this.hide,
createDom: this.$el
},
style: {
left: -1000 + 'px',
top: -1000 + 'px',
}
}, this.$slots.default)
},
})
document.body.appendChild(this.dom.$el);
},
remove () {
if (this.dom !== null) {
document.body.removeChild(this.dom.$el);
}
},
show () {
this.dom.$el.style.display = 'block'
},
hide () {
this.dom.$el.style.display = 'none'
this.$emit('hide')
},
async clickTrigger (e) {
if (this.dom === null) {
await this.create(e)
} else {
this.show()
}
this.$emit('show')
}
},
beforeDestroy () {
this.remove()
},
}
</script>
<style scoped lang="less">
.insertDom {
display: inline-block;
}
</style>
<template>
<div
class="popover"
:class="$props.popperClass"
>
<div
class="close"
@click="close"
v-if="closeBtn"
></div>
<slot></slot>
</div>
</template>
<script>
export default {
props: {
popperClass: {
type: String
},
closeBtn: {
type: Boolean
},
hide: {
type: Function
},
createDom: {
}
},
methods: {
setPosition () {
let posEl = this.$props.createDom.getBoundingClientRect()
let posSelf = this.$el.getBoundingClientRect()
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
let windowWidth = document.documentElement.clientWidth || document.body.clientWidth;
let windowHeight = document.documentElement.clientHeight || document.body.clientHeight;
let posX = 0
let posY = 0
if (windowWidth + scrollLeft - (posEl.width + posEl.left + posSelf.width + 8) > 0) {
posX = posEl.width + posEl.left + 8
} else {
let offsetWidth = windowWidth + scrollLeft - (posEl.width + posEl.left + posSelf.width + 8)
posX = posEl.width + posEl.left + 8 + offsetWidth
}
if (windowHeight + scrollTop - (posEl.height + posEl.top + posSelf.height + 8) > 0) {
posY = posEl.height + posEl.top + 8
} else {
let offsetHeight = windowHeight + scrollTop - (posEl.height + posEl.top + posSelf.height + 8)
posY = posEl.height + posEl.top + 8 + offsetHeight
}
this.$el.style.left = posX + 'px'
this.$el.style.top = posY + 'px'
},
close () {
this.$props.hide()
}
},
mounted () {
setTimeout(() => {
this.setPosition()
}, 100)
this.otherHide = (e) => {
if (!this.$el.contains(e.target) && !this.$props.createDom.contains(e.target)) {
this.$props.hide()
}
}
window.addEventListener('click', this.otherHide)
window.addEventListener('resize', this.setPosition)
},
beforeDestroy () {
window.removeEventListener('click', this.otherHide)
},
}
</script>
<style scoped lang="less">
.popover {
position: absolute;
padding: 8px;
background-color: #fff;
z-index: 2047;
color: #606266;
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
min-width: 150px;
border-radius: 4px;
border: 1px solid #ebeef5;
word-break: break-all;
.close {
position: absolute;
right: 0;
top: 0;
background-color: #ccc;
width: 20px;
height: 20px;
}
}
</style>
|