vue.js自定义指令实现点击waves波纹特效

前言

vue.js的自定义指令的最大优势就是复用,而waves波纹特效可以说是最适合做自定义指令的特效,因为它可以应用在任何按钮或者页面每一个角落里。

本文参考了vue-element-admin的waves特效。

学习自定义指令

可以看我写的《通俗学习Vue.js自定义指令》

然后咱们先做一个不借助自定义指令的DEMO。

按钮waves特效DEMO

准备按钮

准备一个button,用div模拟一个:<div style="width: 100px; height: 50px; line-height: 50px; text-align: center; border: 1px solid #999;" @click="waveMe">我是按钮</div>

waveMe方法的第一部分代码

上面绑定了一个click事件,现在准备waveMe方法,第一部分代码如下,这部分做的事情是:

  • 让按钮relative,这样给按钮插入子元素之后,子元素可以将按钮作为定位祖先。子元素就是产生波纹特效的元素。
  • 给按钮append这个子元素,子元素的class是waves-ripple
  • 子元素的宽高取按钮的最长的一边(例子里是宽最长),这样子元素放大的时候才能充满这个按钮。
      const target = e.target
      target.style.position = 'relative'
      target.style.overflow = 'hidden'
      const rect = target.getBoundingClientRect()
      let ripple = target.querySelector('.waves-ripple')
      if (!ripple) {
        ripple = document.createElement('span')
        ripple.className = 'waves-ripple'
        ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
        target.appendChild(ripple)
      } else {
        ripple.className = 'waves-ripple'
      }

准备CSS

waveMe方法先看上面第一部分,然后看看waves-ripple类是怎么回事:

  • .waves-ripple要绝对定位、圆形、设一个Alpha透明度很透明的黑色背景、先缩小到消失、opacity则完全不透明。opacity是在Alpha透明度的基础上再做透明度计算,也就是乘法。

  • .waves-ripple.z-active是说,一旦加上.z-active,希望实现:用1.2秒时间opacity从1到0,用0.6秒时间从0到放大2倍,两个特效是同时开始的。视觉效果就是:不到0.6秒的时间里,波纹就已经充满了按钮,因为0.6秒的时候已经2倍大了,充满按钮之后还有不到一秒的时间会看到波纹慢慢变淡直到消失。

其实波纹特效本身是没有什么规范的,差不多就可以。

.waves-ripple {
    position: absolute;
    border-radius: 100%;
    background-color: rgba(0, 0, 0, 0.15);
    pointer-events: none;
    user-select: none;
    transform: scale(0);
    opacity: 1;
}

.waves-ripple.z-active {
    opacity: 0;
    transform: scale(2);
    transition: opacity 1.2s ease-out, transform 0.6s ease-out;
}

waveMe方法的第二部分代码

水波的动画上面已经准备好了,现在确定水波的坐标。

这里考虑两种不同的需求,一种是一定要从按钮中心点开始水波扩散,另一种是从真正的点击位置进行鼠标扩散。

      const type = 'center'
      switch (type) {
        case 'center':
          ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
          ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
          break
        default:
          ripple.style.top =
            e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop + 'px'
          ripple.style.left =
            e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft + 'px'
      ripple.className = 'waves-ripple z-active'
center情况

我解释一下ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'

这里面rect.height就是按钮高度,除以2就是半高,ripple.offsetHeight是水波元素的高度,除以2也是元素的半高。怎么理解?我说一种通俗的理解方法:

假如水波元素没有高度,那么,水波元素的top就真的是按钮的半高就可以了。
假如水波元素有10像素高度,那么,top如果还是按钮半高,那么水波元素的中心点就不再是按钮的中心点,而是靠下了,靠下了5像素。所以,水波元素高度如果是N,被挤下去的距离就是N/2,那么top就要向上修正N/2,也就是减去N/2。这么理解就可以了。

其他情况,也就是从点击位置发散水波

以这句代码为例解释一下:

          ripple.style.top =
            e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop + 'px'

e.pageY:鼠标相对于文档坐标(注意,不是相对于浏览器窗口)的纵坐标

rect.top:按钮左上角相对于浏览器窗口的纵坐标

ripple.offsetHeight / 2:水波元素的半高

document.documentElement.scrollTop:滚动条的纵坐标,其实意思就是“页面当前可视部分的上沿,距离页面顶部的距离”

这一连串相减,怎么理解:

先考虑“鼠标相对于文档坐标”减去“滚动条的纵坐标”,等于鼠标相对于浏览器视口的纵坐标,再减去“按钮左上角相对于浏览器窗口的纵坐标”,等于按钮左上角到鼠标的纵距离,再减水波元素的半高,道理跟上面的center场景一样,因为水波有高度,是为了抵消高度带来的偏差,最后等于水波元素的top值。

改写成局部自定义指令

<div v-wave:[wavePoint] style="...">我是按钮</div>
  data() {
    return {
      wavePoint: 'hit' // 或 'center'
    };
  },
  directives: {
    wave: {
      bind(el, binding) {
        el.addEventListener('click', function(e) {
          // 贴 waveMe 的代码段
          // 注意一处改动 const type = binding.arg
        })
      }
    }
  }

改写成全局自定义指令

Vue.directive('wave', {
  bind(el, binding) {
          // 同上,贴 waveMe 的代码段
  }
})

改写成Vue.js的插件

Vue.js的插件编写模式有多种,这里介绍比较流行的一种,做法是在src/directive/wave/中创建三个文件,分别是index.js、wave.css、wave.js:

  1. 创建src/directive/wave/index.js

这是入口文件,负责引入wave.js,并且定义install函数。if (window.Vue)这一句是为了兼容Vue的UMD版本,UMD版本说穿了就是用<script>标签引入Vue,这样Vue是window的一个属性,这时候需要手动Vue.use(install);来使用插件。

// eslint-disable-line这句注释是写给ESLint插件看的,意思是让ESLint插件不要管这一行,如果不加这句注释,那么会报错:'Vue' is not defined (no-undef)

如果你不用考虑UMD方式,那么这句if不需要加。

import wave from './wave'

const install = function(Vue) {
  Vue.directive('wave', wave)
}

if (window.Vue) {
  window.wave = wave
  Vue.use(install); // eslint-disable-line
}

wave.install = install
export default wave
  1. 创建wave.css

这个不多说,上文有贴过代码。

  1. 创建wave.js
import './wave.css'

function handleClick(el, binding) {
  // 逻辑代码,上文有贴
}

export default {
  bind(el, binding) {
    el.addEventListener('click', handleClick(el, binding), false)
  }
}

使用插件

在某个.vue文件中写入:

import wave from '@/directive/wave'

export default {
  directives: { wave },
}

谢谢阅读。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343