未点击时候是这样的
点击按钮,d出d框,d框是主动添加到body下的标签
拉动浏览器窗口,d框可以自动调节显示位置
代码调用和 el-popover 保持一致。
使用具名插槽reference,来显示触发按钮使用默认插槽来显示d框<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) => { // createElement是render的回调参数,作用是创建虚拟dom,
// return 创建的虚拟dom
},
})
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 使用规则
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML attribute
attrs: {
id: 'foo'
},
// 组件 prop
props: {
myProp: 'bar'
},
// DOM property
domProps: {
innerHTML: 'baz'
},
// 事件监听器在 `on` 内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
on: {
click: this.clickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层 property
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.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)
},
})
源码
lm-popover.vue
<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: {
// 创建d框
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);
},
// 删除d框
remove () {
if (this.dom !== null) {
document.body.removeChild(this.dom.$el);
}
},
// 显示d框
show () {
this.dom.$el.style.display = 'block'
},
// 隐藏d框
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')
}
},
// 组件销毁,自动再body中删除对应内容
beforeDestroy () {
this.remove()
},
}
</script>
<style scoped lang="less">
.insertDom {
display: inline-block;
}
</style>
popover.vue
<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>
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)