先看下效果
  
在Vue3中<Teleport> 是一个内置组件,使我们可以将一个组件的一部分模板“传送”到该组件的 DOM 层次结构之外的 DOM 节点中
官网:Teleport传送门
咱们可以使用Teleport实现一件有趣的事情 ,就是B站的视频播放效果。也就是当咱们播放视频,向下滚动至视频离开可视区,会在右下方出现一个小窗口继续咱们的视频播放,不会中断。
其实用Vue3还是蛮好实现的,通过Teleport传送门和useIntersectionObserver API可以轻松实现。这是我之前用vue3写的一个demo,感兴趣可以去瞅瞅喽 使用Vue3中的Teleport API实现B站视频播放效果
接下正题:用Vue2实现同样的效果
咱们首先手写一个Teleport组件 ,能满足咱们的需求就可以。
Teleport组件
<script>
export default {
  props: {
    to: {
      type: String,
      required: true
    },
    disabled: {
      type: Boolean,
      default: true
    }
  },
  inject: ['parent'],
  watch: {
    disabled() {
      if(this.disabled) { // 把当前的DOM元素 向目标元素传送过去
        document.querySelector(this.to).appendChild(this.$el)
      }else { // 调用父组件的方法, 再将当前的DOM元素还原的原来的位置
        this.parent.toSourceDom(this.$el)
      }
    }
  },
  mounted(){
    if(this.disabled) document.querySelector(this.to).appendChild(this.$el)
  },
  /**
   *  render方法中,如果有变量得使用{}
   *  this.$scopedSlots.default() 表示预留一个插槽
   */
  render() {
    return <div class="Teleport">{this.$scopedSlots.default()}</div>
  }
}
</script>
<style scoped>
.Teleport {
  width: 100%;
  height: 100%;
}
</style>接受一个to属性和disabled属性,to属性表示传送到的目标元素dom id或者class类名。disabled表示什么时候要进行传送,true表示传送,false表示不传送
咱们监听 disabled变化就可以,this.$el是当前这个Teleport组件的所有dom节点,通过render函数渲染出dom并预留一个插槽
然后是父组件的逻辑编写:
<template>
  <div id="main">
    
    <!-- 要播放的大视频窗口 -->
    <div id="sourceBox">
      <Teleport v-if="isShow" :disabled="isTeleport" :to="'#targetBox'">
        <VideoModel :id="'VideoModel'" />
      </Teleport>
    </div>
    <!-- 模拟滚动 -->
    <div style="width: 100%;height: 2000px;"></div>
    <!-- 被传送的小视频窗口 -->
    <div id="targetBox"></div>
  </div>
</template>
<script>
import Teleport from '@/components/Teleport/Teleport.vue'
import VideoModel from '@/components/video-model/videoModel'
export default {
  data() {
    return {
      isShow: false,  // 控制 Teleport 组件挂载时机
      isTeleport: false  // 控制什么时候被传送
    }
  },
  provide() {
    return {
      parent: this
    }
  },
  components: { Teleport, VideoModel },
  mounted(){
    this.isShow = true  // 确保传送到的目标元素挂载在 Teleport 组件之前
    document.getElementById('main').addEventListener('scroll', this.intersectionObserver)
  },
  destroyed() {  // 离开页面移除监听
    document.getElementById('main').removeEventListener('scroll', this.intersectionObserver)
  },
  methods: {
    //  监听 sourceBox 是否在可视区域内
    intersectionObserver() {
      let main = document.getElementById('main')
      let sourceBox = document.getElementById('sourceBox')
      if(main.scrollTop >= sourceBox.getBoundingClientRect().height) {  // 传送
        console.log('传-->>')
        this.isTeleport = true
      }else {  // 不传
        this.isTeleport = false
      }
    },
    // 当不需要传送的时候,再挂载到原来的位置
    toSourceDom(dom) {
      document.getElementById('sourceBox').appendChild(dom)
    }
  }
}
</script>
<style scoped>
  #main {
    width: 100%;
    height: 100%;
    position: relative;
    overflow: auto;
  }
  #sourceBox {
    position: absolute;
    width: 640px;
    height: 320px;
    left: 0;
    top: 0;
    border: 1px solid red;
    box-sizing: border-box;
  }
  #targetBox {
    position: fixed;
    right: 50px;
    bottom: 20px;
    width: 320px;
    height: 180px;
    border: 1px solid red;
    box-sizing: border-box;
  }
</style>视频我用的是 videojs,在我vue使用video.js
这篇博客有写,或者把视频去掉,改成文字,看看下传送的效果也是可以的,注意 #main 一定要有宽高,我这里是100% * 100% 根据实际情况稍作修改就好
其实Teleport 本质上也是,appendChild 去目标元素追加dom节点,根据 disabled 判断是向目标元素追加,还是再挂载到原来的位置
                










