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下过滤数组重复值的代码
Sep 10 Javascript
JavaScript国旗变换效果代码
Aug 13 Javascript
JavaScript使用encodeURI()和decodeURI()获取字符串值的方法
Aug 04 Javascript
js使用cookie记录用户名的方法
Nov 26 Javascript
Bootstrap下拉菜单效果实例代码分享
Jun 30 Javascript
相册展示PhotoSwipe.js插件实现
Aug 25 Javascript
JavaScript实现格式化字符串函数String.format
Dec 16 Javascript
Bootstrap选项卡学习笔记分享
Feb 13 Javascript
详解在Angular项目中添加插件ng-bootstrap
Jul 04 Javascript
微信小程序授权登陆及每次检查是否授权实例代码
Sep 18 Javascript
react-intl实现React国际化多语言的方法
Sep 27 Javascript
解决await在forEach中不起作用的问题
Feb 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编程开发“虚拟域名”系统
2006/10/09 PHP
php读取txt文件组成SQL并插入数据库的代码(原创自Zjmainstay)
2012/07/31 PHP
php递归函数三种实现方法及如何实现数字累加
2015/08/07 PHP
UPUPW 更新 64 位 Apache 系列 PHP 7.0 正式版
2015/12/08 PHP
JavaScript函数详解
2015/02/27 Javascript
js动态创建及移除div的方法
2015/06/03 Javascript
jQuery删除当前节点元素
2016/12/07 Javascript
开源免费天气预报接口API及全国所有地区代码(国家气象局提供)
2016/12/26 Javascript
Javascript中的 “&amp;” 和 “|” 详解
2017/02/02 Javascript
JS实现数组去重复值的方法示例
2017/02/18 Javascript
详解jQuery同步Ajax带来的UI线程阻塞问题及解决办法
2017/08/09 jQuery
初试vue-cli使用HBuilderx打包app的坑
2019/07/17 Javascript
JQuery发送ajax请求时中文乱码问题解决
2019/11/14 jQuery
JS数组扁平化、去重、排序操作实例详解
2020/02/24 Javascript
vue实现简易计算器功能
2021/01/20 Vue.js
[00:34]TI7不朽珍藏III——地穴编织者不朽展示
2017/07/15 DOTA
[43:32]Winstrike vs VGJ.S 2018国际邀请赛淘汰赛BO3 第一场 8.23
2018/08/24 DOTA
python sort、sorted高级排序技巧
2014/11/21 Python
Python functools模块学习总结
2015/05/09 Python
在Django的模型中执行原始SQL查询的方法
2015/07/21 Python
python Django框架实现web端分页呈现数据
2019/10/31 Python
win10系统Anaconda和Pycharm的Tensorflow2.0之CPU和GPU版本安装教程
2019/12/03 Python
Python for i in range ()用法详解
2020/09/18 Python
TensorFlow实现自定义Op方式
2020/02/04 Python
Python列表操作方法详解
2020/02/09 Python
Python实现列表中非负数保留,负数转化为指定的数值方式
2020/06/04 Python
Pycharm Available Package无法显示/安装包的问题Error Loading Package List解决
2020/09/18 Python
selenium设置浏览器为headless无头模式(Chrome和Firefox)
2021/01/08 Python
英格兰足协官方商店:England Store
2019/07/12 全球购物
建筑公司文秘岗位职责
2013/11/29 职场文书
实习评语
2013/12/16 职场文书
2014幼儿教师个人工作总结
2014/12/03 职场文书
安全员岗位职责
2015/02/10 职场文书
泰坦尼克号观后感
2015/06/04 职场文书
《赵州桥》教学反思
2016/02/17 职场文书
带你了解CSS基础知识,样式
2021/07/21 HTML / CSS