利用vue3新增的Teleport组件实现一个模态框预览图像

443 阅读2分钟

今天公司项目需要实现图像预览功能,所以尝试自己研究一波~~~~~~

预览图像功能比较简单没有涉及难的东西,本文旨在记录利用Teleport组件实现模态框预览图像的过程。其中包括知识点(Teleport组件的使用、模态框的样式、元素的拖拽以及滚轮控制放大缩小等)下面就逐步实现。

一、 视图层Teleport向body里插入模态框

Teleport组件:

vue3新增的组件,用来实现将元素插入到指定位置。适用于处理元素的代码与元素的位置不在同一组件的情况,例如:在某个组件中控制模态框的显示与隐藏,而模态框在body身上。

   <!-- to属性用来指定插入的位置,类型为 string | HTMLElement -->
   <Teleport to="#app">
      <div class="modal" v-if="previewStatus" @wheel="wheelEvent">
    
        <!-- 此处可以忽略无视, 项目中的pdf预览插件 -->
        <vue-pdf-embed v-draggable :source="state.source" :style="scale" class="vue-pdf-embed" />
        <button class="close" @click.prevent.stop="previewStatus = false">
            <el-icon><Close /></el-icon>
        </button>
        <div class="handle-btns">
            <button class="btn" @click.prevent.stop="pageZoomOut">
              <el-icon><ZoomIn /></el-icon>
            </button>
            &nbsp;&nbsp;
            <button class="btn" @click.prevent.stop="pageZoomIn">
              <el-icon><ZoomOut /></el-icon>
            </button>
        </div>
      </div>
  </Teleport>

二、 methods与模型层


import { ref, computed } from 'vue'

/** 模态框状态 */
const previewStatus = ref<boolean>(false)

/** 滚轮滚动事件 */
function wheelEvent (e:WheelEvent) {
  requestAnimationFrame(() => {
    const wheelModel = e.deltaY > 0 ? 'down' : 'up'
    if (wheelModel === 'up') {
      pageZoomOut()
    } else if (wheelModel === 'down') {
      pageZoomIn()
    }
  })
}

/** 控制图像缩放变量 */
const scale = computed(() => `transform:translate(-50%, -50%) scale(${state.scale});`)

/** 图像放大 */
function pageZoomOut () {
  if (state.scale < 2) {
    state.scale += 0.1
  }
}

/** 图像缩小 */
function pageZoomIn () {
  if (state.scale > 0.5) {
    state.scale -= 0.1
  }
}




/** ggable自定义拖拽指令的实现 */
interface ElType extends HTMLElement {
  $fun?: (e:MouseEvent) => void
}
/**
 * useTo: 拖拽
 */
const draggable = {
  mounted (el:ElType) {
    el.style.cursor = 'grab'
    el.style.position = 'absolute'

    el.$fun = (e:MouseEvent) => {
      e.preventDefault()
      // 计算当前元素距离浏览器左侧上侧的距离
      const disX = e.clientX - el.offsetLeft
      const disY = e.clientY - el.offsetTop

      document.onmousemove = (e:MouseEvent) => {
        // 计算鼠标移动的距离
        const moveLeft = e.clientX - disX
        const moveTop = e.clientY - disY

        // 设置元素移动后的位置
        el.style.left = moveLeft + 'px'
        el.style.top = moveTop + 'px'
        return false
      }

      document.onmouseup = () => {
        document.onmousemove = null
        document.onmouseup = null
      }
    }
    el.addEventListener('mousedown', el.$fun)
  },
  beforeUnmount (el:ElType) {
    if (!el.$fun) return
    el.removeEventListener('mousedown', el.$fun)
    delete el.$fun
  }
}

/** 绑定到vue实例上
import { createApp } from 'vue'
const app = createApp(App)

app.use(install (app: App) {
    app.directive('draggable', draggable)
})

三、 样式部分

.modal {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, .6);
  z-index: 9999;
  overflow: hidden;
  .close {
    position: absolute;
    top: 10px;
    right: 10px;

    width: 46px;
    height: 46px;
    text-align: center;
    line-height: 46px;
    border-radius: 50%;
    font-size: 24px;
    color: #d0cece;
    border: none;
    background-color: rgba(0,0,0, .69);
    cursor: pointer;

    &:hover {
      transform: scale(1.03);
    }
  }
  .handle-btns {
    display: flex;
    justify-content: center;
    align-items: center;

    position: absolute;
    bottom: 68px;
    left: 50%;
    transform: translateX(-50%);
    width: 180px;
    height: 56px;
    line-height: 56px;
    border-radius: 30px;
    background-color: rgba(0, 0, 0, .69);
    .btn {
      width: 40px;
      height: 40px;
      text-align: center;
      line-height: 40px;
      font-size: 25px;
      color: #d0cece;
      background-color: transparent;
      border-radius: 50%;
      border: none;

      cursor: pointer;

      &:hover {
        background-color: rgba(255, 255, 255, .1)
      }
    }
  }
  .vue-pdf-embed {
    position: absolute;
    top: 30%;
    left: 50%;
    transform: translate(-50%, -50%);

    width: 560px;
  }
}

四、 直接展示

20240625_135709 (online-video-cutter.com).gif