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 相关文章推荐
jquery select选中的一个小问题
Oct 11 Javascript
javascript一些实用技巧小结
Mar 18 Javascript
JS替换文本域内的回车示例
Feb 18 Javascript
一个可以增加和删除行的table并可编辑表格中内容
Jun 16 Javascript
angularJS 中input示例分享
Feb 09 Javascript
跟我学习javascript的call(),apply(),bind()与回调
Nov 16 Javascript
JavaScript jquery及AJAX小结
Jan 24 Javascript
手机图片预览插件photoswipe.js使用总结
Aug 25 Javascript
AngularJS中的JSONP实例解析
Dec 01 Javascript
JavaScript中Promise的使用详解
Feb 26 Javascript
JavaScript数据结构之数组的表示方法示例
Apr 12 Javascript
JS实现仿微信支付弹窗功能
Jun 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
php+mysql写的简单留言本实例代码
2008/07/25 PHP
学习php开源项目的源码指南
2014/12/21 PHP
在Debian系统下配置LNMP的教程
2015/07/09 PHP
简单介绍win7下搭建apache+php+mysql开发环境
2015/08/06 PHP
HTML页面如何象ASP一样接受参数
2007/02/07 Javascript
JavaScript 对象成员的可见性说明
2009/10/16 Javascript
jQuery html() in Firefox (uses .innerHTML) ignores DOM changes
2010/03/05 Javascript
IE不支持getElementsByClassName最终完美解决方案
2012/12/17 Javascript
实用的JS正则表达式(手机号码/IP正则/邮编正则/电话等)
2013/01/11 Javascript
浅谈Javascript中的Function与Object
2015/01/26 Javascript
JavaScript搜索字符串并将搜索结果返回到字符串的方法
2015/04/06 Javascript
教你用javascript实现随机标签云效果_附代码
2016/03/16 Javascript
javascript实现dom元素可拖动
2016/03/21 Javascript
使用jQuery Ajax 请求webservice来实现更简练的Ajax
2016/08/04 Javascript
原生js实现ajax方法(超简单)
2016/09/20 Javascript
Vue.js教程之计算属性
2016/11/11 Javascript
JS实现DOM删除节点操作示例
2018/04/04 Javascript
JS实现可针对算术表达式求值的计算器功能示例
2018/09/04 Javascript
[02:11]2016国际邀请赛中国区预选赛最美TA采访现场玩家
2016/06/28 DOTA
[10:21]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Aster 选手采访
2021/03/11 DOTA
Python虚拟环境项目实例
2017/11/20 Python
解决安装python3.7.4报错Can''t connect to HTTPS URL because the SSL module is not available
2019/07/31 Python
Python实现word2Vec model过程解析
2019/12/16 Python
Python docutils文档编译过程方法解析
2020/06/23 Python
一文读懂Python 枚举
2020/08/25 Python
夜大毕业自我鉴定
2013/10/11 职场文书
日语专业个人的求职信
2013/12/03 职场文书
学年末自我鉴定
2014/01/21 职场文书
不打扫卫生检讨书
2014/02/12 职场文书
演讲比赛策划方案
2014/06/11 职场文书
优秀共青团员事迹材料
2014/12/25 职场文书
九华山导游词
2015/02/03 职场文书
楚门的世界观后感
2015/06/03 职场文书
学生会任命书范本
2015/09/21 职场文书
总结一下关于在Java8中使用stream流踩过的一些坑
2021/06/24 Java/Android
SpringBoot集成MongoDB实现文件上传的步骤
2022/04/18 MongoDB