Zapic's Blog
50行实现一个简单的点击涟漪动画

#0 你看,这漂亮的动画!


点击涟漪动画太棒啦!
为了练手,我突然就想自己用原生方法实现一个简单的.
来,让我们一起开始动手写一个.

#1 先来一个按钮

先创建一个按钮:

<button id="clickme" class="my-btn">Click Me</button>

写一些样式:

<button id="clickme" class="my-btn">Click Me</button>
<style>
.my-btn{
    background-color: #e91e63;
    border: none;
    color: white;
    padding: 10px 14px 8px 14px;
    border-radius: 2px;
    font-weight: 500;
    text-transform: uppercase;
}
</style>

好了,简单的按钮创建好了.

#2 涟漪开始!

先造一个涟漪:



<div class="ripple-wave" style="width:50px;height:50px"></div>
<style>
.ripple-wave {
    position: absolute;
    border-radius: 50%;
    display: block;
    background-color: black;
    transition-duration: 3s;
    opacity: 0.3;
    transform: scale(1);
    transition-property: transform, opacity;
    pointer-events: none;
}
</style>

放进按钮里,顺便做一些调整:

<button id="clickme" class="my-btn ripple"><div class="ripple-wave" style="width:50px;height:50px"></div>Click Me</button>
<style>
.my-btn{
    background-color: #e91e63;
    border: none;
    color: white;
    padding: 10px 14px 8px 14px;
    border-radius: 2px;
    font-weight: 500;
    text-transform: uppercase;
}
.ripple{
    overflow:hidden;
    outline:none;
    position:relative;
}
.ripple-wave {
    position: absolute;
    border-radius: 50%;
    display: block;
    background-color: black;
    transition-duration: 3s;
    opacity: 0.3;
    transform: scale(1);
    transition-property: transform, opacity;
    pointer-events: none;
}
</style>

什么调整?

由于需要将涟漪在按钮内进行绝对定位,所以设置按钮为相对定位:position:relative;.
同时为了放在涟漪溢出按钮,所以设置overflow:hidden.
顺便把涟漪改成白色.

#3 接下来?

接下来不得不让JavaScript介入了.
首先看看我们理想中的效果:
理想中
那就分成3个过程:

  1. 按下时在鼠标指针下创建涟漪,使其缓慢扩散.
  2. 松手时使涟漪快速扩散,并让其过渡消失.
  3. 过渡消失后移除涟漪元素.

#4 涟漪开始! (JavaScript.ver)

JavaScript:

let createWaveHandler = (e)=>{
    // 获得目标元素
    let target = e.target;
    // 创建空div
    let wave = document.createElement("div");
    // 计算涟漪的宽高,应为目标元素的最长边
    let wh = (target.offsetWidth > e.srcElement.offsetHeight ? e.srcElement.offsetWidth.toString() : e.srcElement.offsetHeight.toString()) + "px"
    // 计算涟漪位置
    let x = (e.offsetX - parseInt(wh) / 2).toString() + "px";
    let y = (e.offsetY - parseInt(wh) / 2).toString() + "px";
    // 设置涟漪属性
    wave.classList.add("ripple-wave");
    wave.style.top = y;
    wave.style.left = x;
    wave.style.height = wh;
    wave.style.width = wh;
    // 将涟漪放入目标元素
    target.appendChild(wave);
    // 异步设置涟漪扩张属性,使其以动画方式展现
    // 直接设置会使过渡动画无法展示
    setTimeout(() => {
        wave.style.transform = "scale(3)"
    });
}

CSS:

.ripple-wave {
     position: absolute;
     border-radius: 50%;
     display: block;
     background-color: white;
     /* 低速动画 */
     transition-duration: 3s;
     opacity: 0.3;
     /* 开局隐藏 */
     transform: scale(0);
     transition-property: transform, opacity;
     /* 防止触发事件 */
     pointer-events: none;
}

#5 涟漪退散!

JavaScript:

let removeWaveHandler = (e) =>{
    let target = e.target;
    target.childNodes.forEach((v) => {
        if (v.nodeName == "DIV" && v.classList.contains("ripple-wave")) {
            v.classList.add("ripple-wave-end");
            setTimeout(() => {
                v.remove();
            }, 1000);
       }
   })
}

CSS:

.ripple-wave.ripple-wave-end {
     /* 淡出 */
     opacity: 0;
     /* 使变大动画加速溢出 */
     transform: scale(5)!important;
     /* 高速动画 */
     transition-duration: 1s;
}

#6 收尾!

document.getElementById("clickme").addEventListener("mousedown",createWaveHandler);
document.getElementById("clickme").addEventListener("mouseup",removeWaveHandler);

#7 然后?

看成品!

<button id="clickme" class="my-btn ripple">Click Me</button>
<style>
    .my-btn {
        background-color:
            #e91e63;
        border: none;
        color: white;
        padding: 10px 14px 8px 14px;
        border-radius: 2px;
        font-weight: 500;
        text-transform: uppercase;
    }

    .ripple {
        overflow: hidden;
        position: relative;
        outline: none;
    }

    .ripple-wave {
        position: absolute;
        border-radius: 50%;
        display: block;
        background-color: white;
        transition-duration: 3s;
        opacity: 0.3;
        transform: scale(0);
        transition-property: transform, opacity;
        pointer-events: none;
    }

    .ripple-wave.ripple-wave-end {
        opacity: 0;
        transform: scale(5) !important;
        transition-duration: 1s;
    }
</style>
<script>
    let createWaveHandler = (e) => {
        let target = e.target;
        let wave = document.createElement("div");
        let wh = (target.offsetWidth > e.srcElement.offsetHeight ? e.srcElement.offsetWidth.toString() : e
            .srcElement.offsetHeight.toString()) + "px"
        let x = (e.offsetX - parseInt(wh) / 2).toString() + "px";
        let y = (e.offsetY - parseInt(wh) / 2).toString() + "px";
        wave.classList.add("ripple-wave");
        wave.style.top = y;
        wave.style.left = x;
        wave.style.height = wh;
        wave.style.width = wh;
        target.appendChild(wave);
        setTimeout(() => {
            wave.style.transform = "scale(3)"
        });
    }
    let removeWaveHandler = (e) => {
        let target = e.target;
        target.childNodes.forEach((v) => {
            if (v.nodeName == "DIV" && v.classList.contains("ripple-wave")) {
                v.classList.add("ripple-wave-end");
                setTimeout(() => {
                    v.remove();
                }, 1000)
            }
        })
    }
    document.getElementById("clickme").addEventListener("mousedown", createWaveHandler);
    document.getElementById("clickme").addEventListener("mouseup", removeWaveHandler);
</script>