Teleport
之前写
vue3
的时候发现vue3
有个非常好用的内置标签Teleport
;
将一个组件内部的一部分模板“传送”到该组件的DOM
结构外层的位置去。
最近在写 vue2 发现需要用到这个功能,但 vue2 有没有这样的组件,于是自己写了这个组件
<template><div :class="className"><slot /></div>
</template><script>
export default {name: 'teleport',props: {/*** 目标元素,可以是字符串或DOM元素*/to: {type: [String, HTMLElement],required: true},/*** 传送位置,可选值:before、after*/where: {type: String,default: 'after'},/*** 是否禁用传送功能*/disabled: Boolean},data() {return {nodes: [],waiting: false,observer: null,parent: null}},watch: {to: 'maybeMove',where: 'maybeMove',disabled(value) {if (value) {this.disable()this.teardownObserver()} else {this.bootObserver()this.move()}}},mounted() {// Store a reference to the nodesthis.nodes = Array.from(this.$el.childNodes)if (!this.disabled) {this.bootObserver()}// Move slot content to targetthis.maybeMove()},beforeDestroy() {// Move backthis.disable()// Stop observingthis.teardownObserver()},computed: {className() {if (this.disabled) {return ['teleportClass']}return ['teleportClass', 'hidden']}},methods: {maybeMove() {if (!this.disabled) {this.move()}},move() {this.waiting = false// to允许传递一个具体的DOM元素引用,供高级操作使用if (typeof this.to === 'string') {this.parent = document.querySelector(this.to)} else if (this.to) {// 如果他不是字符串,就认为他是个DOM元素引用,直接赋值this.parent = this.to} else {// 无法被识别的目标,不做处理}if (!this.parent) {this.disable()this.waiting = truereturn}if (this.where === 'before') {this.parent.prepend(this.getFragment())} else {this.parent.appendChild(this.getFragment())}},disable() {this.$el.appendChild(this.getFragment())this.parent = null},// Using a fragment is faster because it'll trigger only a single reflow// See https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragmentgetFragment() {const fragment = document.createDocumentFragment()this.nodes.forEach((node) => fragment.appendChild(node))return fragment},onMutations(mutations) {// Makes sure the move operation is only done oncelet shouldMove = falsefor (let i = 0; i < mutations.length; i++) {const mutation = mutations[i]const filteredAddedNodes = Array.from(mutation.addedNodes).filter((node) => !this.nodes.includes(node))if (Array.from(mutation.removedNodes).includes(this.parent)) {this.disable()this.waiting = !this.disabled} else if (this.waiting && filteredAddedNodes.length > 0) {shouldMove = true} else {// 都不满足的情况下,不做任何事}}if (shouldMove) {this.move()}},bootObserver() {if (this.observer) {return}this.observer = new MutationObserver((mutations) =>this.onMutations(mutations))this.observer.observe(document.body, {childList: true,subtree: true,attributes: false,characterData: false})},teardownObserver() {if (this.observer) {this.observer.disconnect()this.observer = null}}}
}
</script><style scoped>
.hidden {display: none;visibility: hidden;
}
</style>