携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情
不知道你是否有见过安卓系统上的这种水波纹涟漪特效,就像下图这样:
思考实现原理
那么这样一个小特效要怎么实现呢?不妨先来思考一下以下几个问题:
- 特效的起点在哪?
- 特效从整体上看应当是什么形状的?
- 特效触发到结束,整个特效生效的过程中有什么要注意的吗?
特效的起点
首先要先找到特效触发的起点,直观上看,也就是鼠标点击的位置,但这个位置是相对触发特效元素,也就是图中的按钮而言的,而不是整个视口而言,所以我们不仅仅需要获取到鼠标的坐标,还需要获取到触发特效元素的坐标,计算一个相对坐标才行
特效的形状
这里由于按钮元素设置了overflow: hidden,所以导致看不出整体形状是怎样的,但是其实也不难猜测,特效的形状应当是一个圆形,这从现实生活中的水波纹也可以看出来
如果把overflow: hidden去掉,再看看特效的样子:
这样是不是更加像水波纹呢?只是为了美观和尽可能还原安卓系统上的水波纹特效,还是需要给父元素设置overflow: hidden
特效生效过程要注意什么?
overflow: hidden应当有还原机制,比如触发特效的元素本身设置的overflow并不是hidden,而是其他值时,如果特效强行改为hidden后就不管了的话,会影响到原本想要的效果,所以应当在特效结束后将overflow恢复到原来的值- 特效应当通过
position: absolute绝对定位放到触发特效元素上,这也就意味着,触发特效的元素的position应为relative,但也和overflow类似,应当在特效结束后将其还原回原本的position,否则强行修改之后就不管了的话,会导致意外的bug
实现
HTML
html结构很简单,就是一个按钮元素
<div class="btn ripple">Button</div>
CSS
css比较简单,我只放出水波涟漪特效相关的部分,按钮和整体布局等样式就没必要贴出来了
.ripple-effect {
position: absolute;
background-color: white;
width: 100px;
height: 100px;
border-radius: 50%;
transform: translate(-50%, -50%) scale(0);
animation: scale 0.5s ease-out;
}
/* 水波涟漪逐渐放大并慢慢消失 */
@keyframes scale {
to {
transform: translate(-50%, -50%) scale(3);
opacity: 0;
}
}
JavaScript
根据前面的分析,写出如下代码,注释都已经写明,直接看即可
(() => {
/** @type { NodeListOf<HTMLElement> } */
const oRipples = document.querySelectorAll(".ripple");
const init = () => {
bindEvent();
};
const bindEvent = () => {
// 遍历所有带有 ripple 特效的元素 -- 添加 ripple 特效
oRipples.forEach((oRipple) => {
oRipple.addEventListener("click", rippleEffect);
});
};
/**
* @description 水波涟漪特效
* @param {MouseEvent} e
*/
const rippleEffect = (e) => {
// 将触发特效的元素的定位设置为 relative -- 这样才能让特效元素定位到触发元素上
// 要记录原始的 position,在特效结束后还原回去
const rawPosition = e.target.style.position;
e.target.style.position = "relative";
// 将触发特效的元素的 overflow 设置为 hidden 防止水波溢出
// 先记录原始的 overflow 然后强行改成 hidden 再在特效结束时将其恢复 避免影响原本的元素
const rawOverflow = e.target.style.overflow;
e.target.style.overflow = "hidden";
// 计算水波涟漪特效的起点 -- 即鼠标点击处相对于触发特效元素的坐标
const [mouseX, mouseY] = [e.clientX, e.clientY];
// 获取触发水波涟漪特效的元素的位置
const [elTop, elLeft] = [e.target.offsetTop, e.target.offsetLeft];
// 计算鼠标点击处相对于触发水波涟漪特效元素的位置
const [relativeX, relativeY] = [mouseX - elLeft, mouseY - elTop];
// 在起点处插入一个 DOM 元素 用于形成水波涟漪特效
const rippleEl = document.createElement("span");
rippleEl.className = "ripple-effect";
// 特效元素的位置正是鼠标相对于触发特效元素的位置
rippleEl.style.left = `${relativeX}px`;
rippleEl.style.top = `${relativeY}px`;
// 添加到触发特效的元素中让特效生效
e.target.appendChild(rippleEl);
// 特效持续时长为 500ms -- 到时间后将特效元素移除
setTimeout(() => {
// 移除特效元素
rippleEl.remove();
// 将触发特效元素的 position 样式还原
e.target.style.position = rawPosition;
// 将触发特效元素的 overflow 样式还原
e.target.style.overflow = rawOverflow;
}, 500);
};
init();
})();