Material Design 风格点击水波纹实现
先来看下效果:example,远古时期写的东西,随便找几个按钮点击即可看到效果。
代码地址:ripple
canvas
绘制和css3
绘制我都实现过,最终还是css效果、性能更佳。
思路
这个其实十分简单,首先分两部分去理解:
css
动画,水波纹的效果是 (透明度1+缩放至0) -> (透明度0+缩放至1),可以用@keyframes
来实现。而波纹的大小交给js
动态设置,再把效果写成指定的calss
,最后js
动态输出该节点即可;先生成一个节点,然后设置波纹的大小,可以根据节点的
宽高
取一个最大值作为波纹的大小,这里波纹永远都是正方形的。然后再鼠标摁下
的时候获取坐标位置,最后输入该节点即可。至于什么时候销毁该节点,可以利用node.addEventListener("animationend", fn)
来监听处理。
css 部分
[ripple] { position: relative; overflow: hidden; } [ripple] .ripple { position: absolute; border-radius: 100%; transform: scale(0); pointer-events: none; animation: ripple .4s ease-out; } @keyframes ripple { to { transform: scale(2); opacity: 0; } } 复制代码
js 部分
这里有个细节,就是水波纹效果结束之后被移除的节点我将他们放到一个数组里面,下次使用的时候直接从数组里面读取,之前做游戏的时候使用的一种节能模式,在网页上还没验证过,不知道有没有真正节省到性能的开销...没有的话直接删除该逻辑操作,可以尽可能的减少代码量。
/** * 水波纹节点对象池 * @type {Array<HTMLElement>} */ const ripplePool = []; /** * 点击水波纹 * @param {TouchEvent | MouseEvent} event 点击事件 * @param {HTMLElement} target 点击目标 */ function ripple(event, target) { /** * 水波纹动画节点 * @type {HTMLElement} */ let node = null; // 从对象池里面拿取节点 if (ripplePool.length > 1) { node = ripplePool.shift(); } else { node = document.createElement("div"); node.className = "ripple"; } /** 点击目标矩阵尺寸 */ const rect = target.getBoundingClientRect(); /** 当前自定义颜色值 */ const color = target.getAttribute("color"); /** 波纹大小 */ let size = Math.max(rect.width, rect.height); // 设置最大范围 if (size > 200) size = 200; // 设置大小 node.style.height = node.style.width = size + "px"; // 默认是白色透明 node.style.backgroundColor = color || "rgba(255, 255, 255, .45)"; // 这里必须输出节点后再设置位置,不然会有问题 target.appendChild(node); const y = event.touches ? event.touches[0].clientY : event.clientY; const x = event.touches ? event.touches[0].clientX : event.clientX; const top = y - rect.top - (node.offsetHeight / 2); const left = x - rect.left - (node.offsetWidth / 2); // console.log(top, left); node.style.top = top + "px"; node.style.left = left + "px"; function end() { node.removeEventListener("animationend", end); // console.log("动画结束", node); target.removeChild(node); ripplePool.push(node); } node.addEventListener("animationend", end); } 复制代码
使用方式
传统网页模式:
<button class="button" ripple>BUTTON-1</button> <button class="button" ripple color="rgba(0,0,0,0.1)">BUTTON-2 设置波纹颜色</button> 复制代码
使用事件代理去完成方法操作,因为节点有可能是动态生成的。
const mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|OperaMini/i.test(navigator.userAgent); /** 添加事件类型 */ const eventType = mobile ? "touchstart" : "mousedown"; document.body.addEventListener(eventType, function (e) { /** 事件类型 */ const event = e || window.event || arguments.callee.caller.arguments[0]; /** 循环的次数 */ let loopCount = 3; // 这里的 3 次是布局的子节点层数,可根据布局层数增加减少 /** * 定义目标变量 * @type {HTMLElement} */ let target = event.target; // 循环 3 次由里向外查找目标节点 while (loopCount > 0 && target && target != document.body) { loopCount--; if (target.hasAttribute("ripple")) { ripple(event, target); break; } target = target.parentNode; } }); 复制代码
vue 项目使用自定义指令模式使用
<button class="button" v-ripple>BUTTON-1</button> 复制代码
Vue.directive("ripple", { inserted(el, binding) { el.setAttribute("ripple", ""); el.addEventListener(eventType, function (e) { ripple(e, el); }); } })
作者:黄景圣
链接:https://juejin.cn/post/7047782205002612773