阅读 2783

老板:你给我来个蜻蜓点水的特效

前言

最近,老板找到我说想搞点花哨的特效,于是乎,列举了各大让人抓狂的特效。

之后在我的深入评估(摸鱼)中,选取了一个稍微简单的特效,所谓蜻蜓点水实际就是波纹特效。

寻找思路

定好特效后,老板拿了张效果图给我:

5c6a9822720e0cf345a6a7dd0d46f21fbe09aa78.jpg

好家伙,虽然我码的功能性的逻辑比较多,但是这种花哨的技能我也是不能落下的,我是基于react来编写该特效(也有vue版本的,后面会放上,有兴趣自行查看),接下来该理理思路,好为接下来的工作(摸鱼)做好准备。

首先,我们知道波纹会从中间扩散开来,且会有多个波纹叠加在一起,之后扩散开来的大小可以随机生成来拟真。

所以我们需要定义纹波圈层数以及波纹的最小最大尺寸,以及波纹的颜色等数据。

先定义一下波纹的配置:

import React from "react";
class App extends React.Component {
    get waveArr() {
            const wavesConfig = { ...this.state.wavesConfig };
            let total = [];
            for (let i = 1; i <= wavesConfig.total; i++) {
                    total.push(i);
            }
            return total;
    }
    constructor(props) {
        super(props);
        this.state = {
                waves: [], // 存放波纹的数组
                wavesConfig: {
                        maxSize: 200, // px,波纹最大尺寸
                        minSize: 100, // px,波纹最小尺寸
                        zIndexCount: 999, // 波纹父元素其实z-index数值
                        waveColor: "#40b6f0", //波纹基础颜色
                        total: 5, //波纹圈层数
                },			
                clickedCount: 0, //统计点击次数(这个后面说)
        };
    }

}
复制代码

有了基本配置后,我们需要一个创建波纹的方法,那这个方法该如何实现呢?

理一下思路,首先新生成的波纹当然是要在之前波纹的上层产生叠加效果,之后给定随机范围内的波纹大小让其生成,并且往波纹数据里push一个新的波纹配置对象。

createWave = (e) => {
        const wavesConfig = { ...this.state.wavesConfig };
        const { waves } = this.state;
        // 让新生成的波纹始终在之前波纹的上层产生叠加效果
        if (wavesConfig.zIndexCount > 99999) {
                wavesConfig.zIndexCount = 999;
        } else {
                wavesConfig.zIndexCount++;
        }
        // 在一定范围内随机生成波纹的大小
        const waveSize = parseInt(
                Math.random() * (wavesConfig.maxSize - wavesConfig.minSize) +
                        wavesConfig.minSize
        );
        //添加新的波纹数据
        waves.push({
                left: `${e.clientX - waveSize / 2}px`,
                top: `${e.clientY - waveSize / 2}px`,
                zIndex: wavesConfig.zIndexCount,
                width: `${waveSize}px`,
                height: `${waveSize}px`,
        });
        this.setState({
                waves,
                wavesConfig,
        });
};
复制代码

有了创建波纹的方法后,当用户点击时候,将调用这个方法创建一个波纹。

document.getElementById("root").onclick = (e) => {
    this.createWave(e);
}

复制代码

那么有了波纹的基本配置,接下来就是波纹的特效,利用css动画和遍历来实现:

<div>
    <div className="main-container">
        <div className="waves">
        {waves.map((w, i) => {
            return (
            <div className="wave" style={{ ...w }} key={`w_${i}`}>
                {this.waveArr.map((n) => {
                    return (
                        <div
                        className="wave-item"
                        key={`wi_${n}`}
                        style={{
                                transform: `scale(${0.1 * Math.sqrt(n - 1)})`,
                                opacity: 0.3 * (1 / n),
                                animationDelay: `${(n - 1) * 0.12}s`,
                                animationDuration: `${0.6 + n * 0.3}s`,
                                backgroundColor: wavesConfig.waveColor,
                        }}
                        />
                    );
                })}
            </div>
            );
        })}
        </div>
    </div>
</div>
复制代码
.waves {
  .wave {
    position: fixed;
    pointer-events: none; // 点击事件穿透,使得鼠标点击可以穿透波纹,兼容ie11及以上
    @keyframes wave {
      to {
        //波纹逐渐扩散变大变透明
        transform: scale(1);
        opacity: 0;
      }
    }
    .wave-item {
      width: 100%;
      height: 100%;
      position: absolute;
      border-radius: 100%;
      animation: wave forwards ease-out;
    }
  }
}
复制代码

将上述代码码完后,随便往一个地方引入该组件(此处略N行代码):

import Wave from './Wave/app.jsx'
render() {
    return (
        <div>
            <Wave/>
        </div>
    )
}
复制代码

跑起项目并点击页面你会看到如下效果:

20210906_113826.gif

为了防止过多dom积累占用内存,需要定时清理波纹内的数据:

componentDidMount() {
        const { clickedCount, waves } = this.state;
        let num = clickedCount;
        document.getElementById("root").onclick = (e) => {
                num++; // 统计点击次数
                this.setState({
                        clickedCount: num,
                });
                this.createWave(e);
        };
        let lastCount = 0;
        // 2秒内无点击清空waves,防止过多的dom累积占用内存
        setInterval(() => {
                if (lastCount === clickedCount) {
                        console.log("hi");
                        console.log(clickedCount);
                        this.setState({
                                waves: [],
                        });
                }
                lastCount = clickedCount;
        }, 2000);
}
复制代码

完整代码

app.jsx

import React from "react";
class App extends React.Component {
    get waveArr() {
            const wavesConfig = { ...this.state.wavesConfig };
            let total = [];
            for (let i = 1; i <= wavesConfig.total; i++) {
                    total.push(i);
            }
            return total;
    }
    constructor(props) {
        super(props);
        this.state = {
                waves: [], // 存放波纹的数组
                wavesConfig: {
                        maxSize: 200, // px,波纹最大尺寸
                        minSize: 100, // px,波纹最小尺寸
                        zIndexCount: 999, // 波纹父元素其实z-index数值
                        waveColor: "#40b6f0", //波纹基础颜色
                        total: 5, //波纹圈层数
                },			
                clickedCount: 0, //统计点击次数(这个后面说)
        };
    }
    componentDidMount() {
        const { clickedCount, waves } = this.state;
        let num = clickedCount;
        document.getElementById("root").onclick = (e) => {
                num++; // 统计点击次数
                this.setState({
                        clickedCount: num,
                });
                this.createWave(e);
        };
        let lastCount = 0;
        // 2秒内无点击清空waves,防止过多的dom累积占用内存
        setInterval(() => {
                if (lastCount === clickedCount) {
                        console.log("hi");
                        console.log(clickedCount);
                        this.setState({
                                waves: [],
                        });
                }
                lastCount = clickedCount;
        }, 2000);
    }
    createWave = (e) => {
        const wavesConfig = { ...this.state.wavesConfig };
        const { waves } = this.state;
        // 让新生成的波纹始终在之前波纹的上层产生叠加效果
        if (wavesConfig.zIndexCount > 99999) {
                wavesConfig.zIndexCount = 999;
        } else {
                wavesConfig.zIndexCount++;
        }
        // 在一定范围内随机生成波纹的大小
        const waveSize = parseInt(
                Math.random() * (wavesConfig.maxSize - wavesConfig.minSize) +
                        wavesConfig.minSize
        );
        //添加新的波纹数据
        waves.push({
                left: `${e.clientX - waveSize / 2}px`,
                top: `${e.clientY - waveSize / 2}px`,
                zIndex: wavesConfig.zIndexCount,
                width: `${waveSize}px`,
                height: `${waveSize}px`,
        });
        this.setState({
                waves,
                wavesConfig,
        });
    };
    render() {
        const { waves, wavesConfig } = this.state;
        return (
            <div>
                <div className="main-container">
                    <div className="waves">
                        {waves.map((w, i) => {
                            return (
                            <div className="wave" style={{ ...w }} key={`w_${i}`}>
                                {this.waveArr.map((n) => {
                                    return (
                                        <div
                                        className="wave-item"
                                        key={`wi_${n}`}
                                        style={{
                                                transform: `scale(${0.1 * Math.sqrt(n - 1)})`,
                                                opacity: 0.3 * (1 / n),
                                                animationDelay: `${(n - 1) * 0.12}s`,
                                                animationDuration: `${0.6 + n * 0.3}s`,
                                                backgroundColor: wavesConfig.waveColor,
                                        }}
                                        />
                                    );
                                })}
                            </div>
                            );
                        })}
                    </div>
                </div>
            </div>
        )
    }

}
复制代码

app.less

.waves {
  .wave {
    position: fixed;
    pointer-events: none; // 点击事件穿透,使得鼠标点击可以穿透波纹,兼容ie11及以上
    @keyframes wave {
      to {
        //波纹逐渐扩散变大变透明
        transform: scale(1);
        opacity: 0;
      }
    }
    .wave-item {
      width: 100%;
      height: 100%;
      position: absolute;
      border-radius: 100%;
      animation: wave forwards ease-out;
    }
  }
}
复制代码

最后

以上故事纯属虚构,出自作者开源项目的组件,也许不是最佳写法,欢迎提出建议。

react版源码:react-dark-photo

vue版源码:vue-dark-photo

都看到这了,不给的赞吗?

文章分类
前端
文章标签