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


Posted in Javascript onOctober 18, 2018

菜鸟学习之路

//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 指令之气泡提示效果的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript 申明函数的三种方法 每个函数就是一个对象(一)
Dec 04 Javascript
event.currentTarget与event.target的区别介绍
Dec 31 Javascript
cookie 最近浏览记录(中文escape转码)具体实现
Jun 08 Javascript
jQuery中nextAll()方法用法实例
Jan 07 Javascript
js实现使用鼠标拖拽切换图片的方法
May 04 Javascript
JavaScript的面向对象编程基础
Aug 13 Javascript
Jquery日历插件制作简单日历
Oct 28 Javascript
微信小程序 二维码canvas绘制实例详解
Jan 06 Javascript
JavaScript中清空数组的三种方式
Mar 22 Javascript
Angularjs 1.3 中的$parse实例代码
Sep 14 Javascript
为vue项目自动设置请求状态的配置方法
Jun 09 Javascript
layer弹出层取消遮罩的方法
Sep 25 Javascript
使用JS获取页面上的所有标签
Oct 18 #Javascript
深入理解 Koa 框架中间件原理
Oct 18 #Javascript
JS实现十分钟倒计时代码实例
Oct 18 #Javascript
基于Vue实现可以拖拽的树形表格实例详解
Oct 18 #Javascript
JavaScript的词法结构精华篇
Oct 17 #Javascript
Javascript中parseInt的正确使用方式
Oct 17 #Javascript
教你如何编写Vue.js的单元测试的方法
Oct 17 #Javascript
You might like
基于jquery的文本框与autocomplete结合使用(asp.net+json)
2012/05/30 Javascript
javascript中的=等号个数问题两个跟三个有什么区别
2013/10/23 Javascript
php显示当前文件所在的文件以及文件夹所有文件以树形展开
2013/12/13 Javascript
js将控件隐藏及display属性的使用介绍
2013/12/30 Javascript
Node.js的Web模板引擎ejs的入门使用教程
2016/06/06 Javascript
jQuery实现最简单的切换图效果【可兼容IE6、火狐、谷歌、opera等】
2016/09/04 Javascript
省市区三级联动jquery实现代码
2020/04/15 Javascript
bootstrapValidator 重新启用提交按钮的方法
2017/02/20 Javascript
微信小程序支付之c#后台实现方法
2017/10/19 Javascript
详解离线安装npm包的几种方法
2018/11/25 Javascript
Vue入门之数量加减运算操作示例
2018/12/11 Javascript
jQuery - AJAX load() 实例用法详解
2019/08/27 jQuery
Vue实例的对象参数options的几个常用选项详解
2019/11/08 Javascript
vue props对象validator自定义函数实例
2019/11/13 Javascript
vue制作抓娃娃机的示例代码
2020/04/17 Javascript
Vant+postcss-pxtorem 实现浏览器适配功能
2021/02/05 Javascript
python获得两个数组交集、并集、差集的方法
2015/03/27 Python
python制作小说爬虫实录
2017/08/14 Python
python将邻接矩阵输出成图的实现
2019/11/21 Python
如何通过python实现人脸识别验证
2020/01/17 Python
python wav模块获取采样率 采样点声道量化位数(实例代码)
2020/01/22 Python
Python pandas如何向excel添加数据
2020/05/22 Python
Pycharm中配置远程Docker运行环境的教程图解
2020/06/11 Python
几款Python编译器比较与推荐(小结)
2020/10/15 Python
使用Python实现音频双通道分离
2020/12/25 Python
html+js 实现markdown编辑器效果
2019/10/23 HTML / CSS
机械工程系毕业生求职信
2013/09/27 职场文书
公务员年总结的自我评价
2013/10/25 职场文书
秦兵马俑教学反思
2014/02/07 职场文书
论文诚信承诺书
2014/05/23 职场文书
关于随地扔垃圾的检讨书
2014/09/30 职场文书
八一建军节慰问信
2015/02/14 职场文书
2015年班主任个人工作总结
2015/03/31 职场文书
公司业务员管理制度
2015/08/05 职场文书
2019思想汇报范文
2019/05/21 职场文书
CSS浮动引起的高度塌陷问题
2022/08/05 HTML / CSS