vue 指令---气泡提示(手撸实战)
菜鸟学习之路
//L6zt github
自己在造组件轮子,也就是瞎搞。
自己写了个slider组件,想加个气泡提示。为了复用和省事特此写了个指令来解决。
预览地址
项目地址 github 我叫给它胡博
我对指令的理解: 前不久看过 一部分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
}
};
原文地址: https://segmentfault.com/a/1190000016719995
vue 指令---气泡提示(手撸实战)的更多相关文章
- CSS实现自定义手型气泡提示
实现自定义的手型气泡提示 <html> <head> <meta charset="utf-8"> <title></titl ...
- 【前端vue开发】Hbuilder配置Avalon、AngularJS、Vue指令提示
偶尔也会研究一下前端内容,因为Hbuilder是基于eclipse开发的,所以用起来倍感亲切啊,而且在我尝试使用的几款前端开发工具中,Hbuilder的表现也是相当出色地,可以访问Huilder官网下 ...
- 【转】Hbuilder配置Avalon、Vue指令提示
转载自CSDN http://blog.csdn.net/jianggujin/article/details/71419828 我本人是一名Java后端开发,偶尔也会研究一下前端内容,因为Hbuil ...
- Vue指令总结---小白同学必看
今天主要复习一下我们最熟悉vue指令,想要代码撸得快,就要多看书,多看看官方的文档和学习指令,学习编程是一个非常享受的过程,尤其是你不断地去解决问题,获得一项技能,实现薪水的上涨.进行Vue的指令烹饪 ...
- 手撸基于swoole 的分布式框架 实现分布式调用(20)讲
最近看的一个swoole的课程,前段时间被邀请的参与的这个课程 比较有特点跟一定的深度,swoole的实战教程一直也不多,结合swoole构建一个新型框架,最后讲解如何实现分布式RPC的调用. 内容听 ...
- .NET手撸2048小游戏
.NET手撸2048小游戏 2048是一款益智小游戏,得益于其规则简单,又和2的倍数有关,因此广为人知,特别是广受程序员的喜爱. 本文将再次使用我自制的"准游戏引擎"FlysEng ...
- Vue框架(一)——Vue导读、Vue实例(挂载点el、数据data、过滤器filters)、Vue指令(文本指令v-text、事件指令v-on、属性指令v-bind、表单指令v-model)
Vue导读 1.Vue框架 vue是可以独立完成前后端分离式web项目的js框架 三大主流框架之一:Angular.React.Vue vue:结合其他框架优点.轻量级.中文API.数据驱动.双向绑定 ...
- [Vue] : Vue指令
Vue指令之 v-cloak v-cloak是解决解决插值表达式的闪烁问题 . 给插值表达式的元素加上v-cloak <p v-cloak>{{ msg }}</p> 为v-c ...
- 第三篇:Vue指令
Vue指令 1.文本指令相关 v-*是Vue指令,会被vue解析,v-text="num"中的num是变量(指令是有限的,不可以自定义) v-text是原样输出渲染内容,渲染控制的 ...
随机推荐
- (DP)51NOD 1006 最长公共子序列&1092 回文字符串
1006 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abc ...
- jQuery同时监听两个事件---实现同时操控两个按键
我们都知道因为js是单线程的,所以没有可以同时触发键盘两个事件的方法 今天我们就来做一个可以实现这个功能方法 先来看一下成品图效果 接下来我们来看下具体是怎么实现的 注释写在了代码里面 <!DO ...
- 分布式集群环境下,如何实现session共享四(部署项目测试)
这是分布式集群环境下,如何实现session共享系列的第四篇.在上一篇:分布式集群环境下,如何实现session共享三(环境搭建)中,已经准备好了相关的环境:tomcat.nginx.redis.本篇 ...
- java容器章节总结
- DOM编程练习(慕课网题目)
编程练习 制作一个表格,显示班级的学生信息. 要求: 1. 鼠标移到不同行上时背景色改为色值为 #f2f2f2,移开鼠标时则恢复为原背景色 #fff 2. 点击添加按钮,能动态在最后添加一行 3. 点 ...
- python中 import 和from ... import 的区别
先看一个例子: 我自定义的一个moudle,里面有一个方法sayhi,还有一个变量version#!/usr/bin/env python # coding=utf-8 # Filename: mym ...
- 我来解数独(附delphi源码)
前段时间看到“69岁农民3天破解世界最难数独游戏”,然后在看了那个号称世界最难的数独题目之后,就打算抽空编程解决.今晚抽出一个晚上,大约四五个小时的时间,中间还间歇在clash of clans上造兵 ...
- PHP一句话后门过狗姿势万千之传输层加工
既然木马已就绪,那么想要利用木马,必然有一个数据传输的过程,数据提交是必须的,数据返回一般也会有的,除非执行特殊命令. 当我们用普通菜刀连接后门时,数据时如何提交的,狗狗又是如何识别的,下面结合一个实 ...
- (转)搭建Spring4.x.x开发环境
http://blog.csdn.net/yerenyuan_pku/article/details/52831306 先去Spring官网下载Spring4.x.x开发包(本人使用的版本是Sprin ...
- 设计模式、SOLID原则:组件与联系
组件原则 - SRP The Single Responsibility Principle 单一责任原则 当需要修改某个类的时候原因有且只有一个.换句话说就是让一个类只做一种类型的责任,当这个类需要 ...