欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

vue 指令之气泡提示效果的实现代码

程序员文章站 2023-10-30 15:44:04
菜鸟学习之路 //l6zt github 自己 在造组件*,也就是瞎搞。 自己写了个slider组件,想加个气泡提示。为了复用和省事特此写了个指令来解决。 项...

菜鸟学习之路

//l6zt github

自己 在造组件*,也就是瞎搞。

自己写了个slider组件,想加个气泡提示。为了复用和省事特此写了个指令来解决。

项目地址 github 我叫给它胡博

vue 指令之气泡提示效果的实现代码

效果图片

我对指令的理解: 前不久看过 一部分vnode实现源码,奈何资质有限...看不懂。

vnode的生命周期-----> 生成前、生成后、生成真正dom、更新 vnode、更新dom 、 销毁。

而vue的指令则是依赖于vnode 的生命周期, 无非也是有以上类似的钩子。

代码效果

指令挂a元素上,默认生成一个气泡容器b插入到 body 里面,b 会获取 元素 a 的位置信息 和 自己的
大小信息,经过 一些列的运算,b 元素会定位到 a 的 中间 上 位置。 当鼠标放到 a 上 b 就会显示出来,离开就会消失。

以下代码

气泡指令

import { on , off , once, contains, elemoffset, position, addclass, removeclass } from '../utils/dom';
import vue from 'vue'
const global = window;
const doc = global.document;
const top = 15;
export default {
 name : 'jc-tips' ,
 bind (el , binding , vnode) {
  // 确定el 已经在页面里 为了获取el 位置信信 
  vue.nexttick(() => {
   const { context } = vnode;
   const { expression } = binding;
   // 气泡元素根结点
   const fwarpelm = doc.createelement('div');
   // handlefn 气泡里的子元素(自定义)
   const handlefn = binding.expression && context[expression] || (() => '');
   const createelm = handlefn();
   fwarpelm.classname = 'hide jc-tips-warp';
   fwarpelm.appendchild(createelm);
   doc.body.appendchild(fwarpelm);
   // 给el 绑定元素待其他操作用
   el._tipelm = fwarpelm;
   el._createelm = createelm;
   // 鼠标放上去的 回调函数
   el._tip_hover_fn = function(e) {
    // 删除节点函数
     removeclass(fwarpelm, 'hide');
     fwarpelm.style.opacity = 0;
     // 不加延迟 fwarpelm的大小信息 (元素大小是 0 0)---> 删除 class 不是立即渲染
     settimeout(() => {
      const offset = elemoffset(fwarpelm);
      const location = position(el);
      fwarpelm.style.csstext = `left: ${location.left - offset.width / 2}px; top: ${location.top - top - offset.height}px;`;
      fwarpelm.style.opacity = 1;
     }, 16);
   };
   //鼠标离开 元素 隐藏 气泡
   const handleleave = function (e) {
    fwarpelm.style.opacity = 0;
    // transitionend 不太好应该加入兼容
    once({
     el,
     type: 'transitionend',
     fn: function() {
      console.log('hide');
      addclass(fwarpelm, 'hide');
     }
    })
   };
   el._tip_leave_fn = handleleave;
   // 解决 slider 移动结束 提示不消失
   el._tip_mouse_up_fn = function (e) {
    const target = e.target;
    console.log(target);
    if (!contains(fwarpelm, target) && el !== target) {
     handleleave(e)
    }
   };
   on({
    el,
    type: 'mouseenter',
    fn: el._tip_hover_fn
   });
   on({
    el,
    type: 'mouseleave',
    fn: el._tip_leave_fn
   });
   on({
    el: doc.body,
    type: 'mouseup',
    fn: el._tip_mouse_up_fn
   })
  });
 } ,
 // 气泡的数据变化 依赖于 context[expression] 返回的值
 componentupdated(el , binding , vnode) {
  const { context } = vnode;
  const { expression } = binding;
  const handlefn = expression && context[expression] || (() => '');
  vue.nexttick( () => {
   const createnode = handlefn();
   const fwarpelm = el._tipelm;
   if (fwarpelm) {
    fwarpelm.replacechild(createnode, el._createelm);
    el._createelm = createnode;
    const offset = elemoffset(fwarpelm);
    const location = position(el);
    fwarpelm.style.csstext = `left: ${location.left - offset.width / 2}px; top: ${location.top - top - offset.height}px;`;
   }
  })
 },
 // 删除 事件
 unbind (el , bind , vnode) {
  off({
   el: dov.body,
   type: 'mouseup',
   fn: el._tip_mouse_up_fn
  });
  el = null;
 }
}

slider 组件

<template>
  <div class="jc-slider-cmp">
    <section
        class="slider-active-bg"
        :style="{width: `${left}px`}"
      >
    </section>
      <i
          class="jc-slider-dot"
          :style="{left: `${left}px`}"
          ref="dot"
          @mousedown="movestart"
          v-jc-tips="createnode"
      >
      </i>
  </div>
</template>
<script>
 import {elemoffset, on, off, once} from "../../utils/dom";
 const global = window;
 const doc = global.document;
 export default {
  props: {
   step: {
    type: [number],
    default: 0
   },
   rangeend: {
    type: [number],
    required: true
   },
   value: {
    type: [number],
    required: true
   },
   minvalue: {
    type: [number],
    required: true
   },
   maxvalue: {
    type: [number],
    required: true
   }
  },
  data () {
   return {
    startx: null,
    width: null,
    curvalue: 0,
    curstep: 0,
    left: 0,
    templeft: 0
   }
  },
  computed: {
   wtov () {
    let step = this.step;
    let width = this.width;
    let rangeend = this.rangeend;
    if (width) {
     if (step) {
      return width / (rangeend / step)
     }
     return width / rangeend
    }
    return null
   },
   postvalue () {
    let value = null;
    if (this.step) {
     value = this.step * this.curstep;
    } else {
     value = this.left / this.wtov;
    }
    return value;
   }
  },
  watch: {
    value: {
     handler (value) {
      this.$nexttick(() => {
       let step = this.step;
       let wtov = this.wtov;
       if (step) {
        this.left = value / step * wtov;
       } else {
        this.left = value * wtov;
       }
      })
     },
     immediate: true
    }
  },
  methods: {
   movestart (e) {
    e.preventdefault();
    const body = window.document.body;
    const _this = this;
    this.startx = e.pagex;
    this.templeft = this.left;
    on({
     el: body,
     type: 'mousemove',
     fn: this.moving
    });
    once({
     el: body,
     type: 'mouseup',
     fn: function() {
      console.log('end');
      _this.$emit('input', _this.postvalue);
      off({
       el: body,
       type: 'mousemove',
       fn: _this.moving
      })
     }
    })
   },
   moving(e) {
    let curx = e.pagex;
    let step = this.step;
    let rangeend = this.rangeend;
    let width = this.width;
    let templeft = this.templeft;
    let startx = this.startx;
    let wtov = this.wtov;
    if (step !== 0) {
     let all = parseint(rangeend / step);
     let curstep = (templeft + curx - startx) / wtov;
     curstep > all && (curstep = all);
     curstep < 0 && (curstep = 0);
     curstep = math.round(curstep);
     this.curstep = curstep;
     this.left = curstep * wtov;
    } else {
     let left = templeft + curx - startx;
     left < 0 && (left = 0);
     left > width && (left = width);
     this.left = left;
    }
   },
   createnode () {
    const felem = document.createelement('i');
    const textnode = document.createtextnode(this.postvalue.tofixed(2));
    felem.appendchild(textnode);
    return felem;
   }
  },
  mounted () {
   this.width = elemoffset(this.$el).width;
  }
 };
</script>
<style lang="scss">
  .jc-slider-cmp {
    position: relative;
    display: inline-block;
    width: 100%;
    border-radius: 4px;
    height: 8px;
    background: #ccc;
    .jc-slider-dot {
      position: absolute;
      display: inline-block;
      width: 15px;
      height: 15px;
      border-radius: 50%;
      left: 0;
      top: 50%;
      transform: translate(-50%, -50%);
      background: #333;
      cursor: pointer;
    }
    .slider-active-bg {
      position: relative;
      height: 100%;
      border-radius: 4px;
      background: red;
    }
  }
</style>
../utils/dom
const global = window;
export const on = ({el, type, fn}) => {
     if (typeof global) {
       if (global.addeventlistener) {
         el.addeventlistener(type, fn, false)
      } else {
         el.attachevent(`on${type}`, fn)
      }
     }
  };
  export const off = ({el, type, fn}) => {
    if (typeof global) {
      if (global.removeeventlistener) {
        el.removeeventlistener(type, fn)
      } else {
        el.detachevent(`on${type}`, fn)
      }
    }
  };
  export const once = ({el, type, fn}) => {
    const hyfn = (event) => {
      try {
        fn(event)
      }
       finally {
        off({el, type, fn: hyfn})
      }
    }
    on({el, type, fn: hyfn})
  };
  // 最后一个
  export const fbtwice = ({fn, time = 300}) => {
    let [ctime, k] = [null, null]
    // 获取当前时间
    const gettime = () => new date().gettime()
    // 混合函数
    const hyfn = () => {
      const ags = argments
      return () => {
        cleartimeout(k)
        k = ctime = null
        fn(...ags)
      }
    };
    return () => {
      if (ctime == null) {
        k = settimeout(hyfn(...arguments), time)
        ctime = gettime()
      } else {
        if ( gettime() - ctime < 0) {
          // 清除之前的函数堆 ---- 重新记录
          cleartimeout(k)
          k = null
          ctime = gettime()
          k = settimeout(hyfn(...arguments), time)
        }
      }}
  };
  export const contains = function(parentnode, childnode) {
    if (parentnode.contains) {
      return parentnode !== childnode && parentnode.contains(childnode)
    } else {
      // https://developer.mozilla.org/zh-cn/docs/web/api/node/comparedocumentposition
      return (parentnode.comparedocumentposition(childnode) === 16)
    }
  };
  export const addclass = function (el, classname) {
    if (typeof el !== "object") {
      return null
    }
    let classlist = el['classname']
    classlist = classlist === '' ? [] : classlist.split(/\s+/)
    if (classlist.indexof(classname) === -1) {
      classlist.push(classname)
      el.classname = classlist.join(' ')
    }
  };
  export const removeclass = function (el, classname) {
    let classlist = el['classname']
    classlist = classlist === '' ? [] : classlist.split(/\s+/)
    classlist = classlist.filter(item => {
      return item !== classname
    })
    el.classname =   classlist.join(' ')
  };
  export const delay = ({fn, time}) => {
    let ot = null
    let k = null
    return () => {
      // 当前时间
      let ct = new date().gettime()
      const fixfn = () => {
        k = ot = null
        fn()
      }
      if (k === null) {
        ot = ct
        k = settimeout(fixfn, time)
        return
      }
      if (ct - ot < time) {
        ot = ct
        cleartimeout(k)
        k = settimeout(fixfn, time)
      }
    }
  };
  export const position = (son, parent = global.document.body) => {
    let top = 0;
    let left = 0;
    let offsetparent = son;
    while (offsetparent !== parent) {
      let dx = offsetparent.offsetleft;
      let dy = offsetparent.offsettop;
      let old = offsetparent;
      if (dx === null) {
        return {
          flag: false
        }
      }
      left += dx;
      top += dy;
   offsetparent = offsetparent.offsetparent;
      if (offsetparent === null && old !== global.document.body) {
        return {
          flag: false
        }
      }
    }
    return {
      flag: true,
      top,
      left
    }
  };
  export const getelem = (filter) => {
    return array.from(global.document.queryselectorall(filter));
  };
  export const elemoffset = (elem) => {
    return {
      width: elem.offsetwidth,
      height: elem.offsetheight
    }
  };

总结

以上所述是小编给大家介绍的vue 指令之气泡提示效果的实现代码,希望对大家有所帮助