preface

Recently, my boss came to me and said he wanted to do some fancy special effects.

Later in my in-depth evaluation (fishing), I selected a slightly simple effect, the so-called dragonfly water is actually a ripple effect.

Looking for ideas

After deciding the special effects, my boss gave me a sketch:

I wrote it based on React (there is also a VUE version). Now I need to get my mind in order to get ready for the work ahead.

First, we know that the ripples spread out from the middle, and that there are multiple ripples on top of each other, and that the size of the ripples can then be randomly generated to simulate.

Therefore, we need to define the number of ripple circles, the minimum and maximum size of the ripple, and the color of the ripple.

Define the ripple configuration first:

import React from "react";
class App extends React.Component {
    get waveArr() {
            constwavesConfig = { ... 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: [].// Store the array of ripples
                wavesConfig: {
                        maxSize: 200.// the maximum size of the ripple
                        minSize: 100.// px, minimum size of ripple
                        zIndexCount: 999.// The wavy parent element is actually z-index
                        waveColor: "#40b6f0".// Wave base color
                        total: 5.// The number of circles
                },			
                clickedCount: 0.// Count the number of clicks}; }}Copy the code

Now that we have the basic configuration, we need a method to create ripples. How do we implement this method?

First, of course, the newly generated ripple is superimposed on top of the previous ripple, then given a random range of ripple size, it is generated, and a new ripple configuration object is pushed into the ripple data.

createWave = (e) = > {
        constwavesConfig = { ... this.state.wavesConfig };const { waves } = this.state;
        // Make the newly generated ripple always overlay on top of the previous ripple
        if (wavesConfig.zIndexCount > 99999) {
                wavesConfig.zIndexCount = 999;
        } else {
                wavesConfig.zIndexCount++;
        }
        // Generate a random ripple size within a certain range
        const waveSize = parseInt(
                Math.random() * (wavesConfig.maxSize - wavesConfig.minSize) +
                        wavesConfig.minSize
        );
        // Add new ripple data
        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,
        });
};
Copy the code

With a method to create a ripple, this method is called to create a ripple when the user clicks.

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

Copy the code

So with the basic configuration of the ripple, the ripple effect is implemented using CSS animation and traversal:

<div>
    <div className="main-container">
        <div className="waves">
        {waves.map((w, i) => {
            return (
            <div className="wave" style={{ . w }} key={`w_The ${i} `} >
                {this.waveArr.map((n) => {
                    return (
                        <div
                        className="wave-item"
                        key={`wi_The ${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>
Copy the code
.waves {
  .wave {
    position: fixed;
    pointer-events: none; // Click event penetration, so that mouse click can penetrate ripples, compatible with IE11 and above@keyframes wave {
      to{// The ripple becomes larger and more transparenttransform: scale(1);
        opacity: 0; }}.wave-item {
      width: 100%;
      height: 100%;
      position: absolute;
      border-radius: 100%;
      animation: wave forwards ease-out; }}}Copy the code

After completing the above code, import the component anywhere (N lines omitted here) :

import Wave from './Wave/app.jsx'
render() {
    return (
        <div>
            <Wave/>
        </div>)}Copy the code

Run the project and click on the page you should see something like this:

In order to prevent excessive DOM accumulation and occupy memory, it is necessary to periodically clean the data in the ripple:

componentDidMount() {
        const { clickedCount, waves } = this.state;
        let num = clickedCount;
        document.getElementById("root").onclick = (e) = > {
                num++; // Count clicks
                this.setState({
                        clickedCount: num,
                });
                this.createWave(e);
        };
        let lastCount = 0;
        // Clear waves without clicking for 2 seconds to prevent excessive DOM accumulation
        setInterval(() = > {
                if (lastCount === clickedCount) {
                        console.log("hi");
                        console.log(clickedCount);
                        this.setState({
                                waves: [],}); } lastCount = clickedCount; },2000);
}
Copy the code

The complete code

app.jsx

import React from "react";
class App extends React.Component {
    get waveArr() {
            constwavesConfig = { ... 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: [].// Store the array of ripples
                wavesConfig: {
                        maxSize: 200.// the maximum size of the ripple
                        minSize: 100.// px, minimum size of ripple
                        zIndexCount: 999.// The wavy parent element is actually z-index
                        waveColor: "#40b6f0".// Wave base color
                        total: 5.// The number of circles
                },			
                clickedCount: 0.// Count the number of clicks
        };
    }
    componentDidMount() {
        const { clickedCount, waves } = this.state;
        let num = clickedCount;
        document.getElementById("root").onclick = (e) = > {
                num++; // Count clicks
                this.setState({
                        clickedCount: num,
                });
                this.createWave(e);
        };
        let lastCount = 0;
        // Clear waves without clicking for 2 seconds to prevent excessive DOM accumulation
        setInterval(() = > {
                if (lastCount === clickedCount) {
                        console.log("hi");
                        console.log(clickedCount);
                        this.setState({
                                waves: [],}); } lastCount = clickedCount; },2000);
    }
    createWave = (e) = > {
        constwavesConfig = { ... this.state.wavesConfig };const { waves } = this.state;
        // Make the newly generated ripple always overlay on top of the previous ripple
        if (wavesConfig.zIndexCount > 99999) {
                wavesConfig.zIndexCount = 999;
        } else {
                wavesConfig.zIndexCount++;
        }
        // Generate a random ripple size within a certain range
        const waveSize = parseInt(
                Math.random() * (wavesConfig.maxSize - wavesConfig.minSize) +
                        wavesConfig.minSize
        );
        // Add new ripple data
        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_The ${i} `} >
                                {this.waveArr.map((n) => {
                                    return (
                                        <div
                                        className="wave-item"
                                        key={`wi_The ${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>)}}Copy the code

app.less

.waves {
  .wave {
    position: fixed;
    pointer-events: none; // Click event penetration, so that mouse click can penetrate ripples, compatible with IE11 and above
    @keyframes wave {
      to {
        // The ripples gradually spread and become larger and more transparent
        transform: scale(1);
        opacity: 0;
      }
    }
    .wave-item {
      width: 100%;
      height: 100%;
      position: absolute;
      border-radius: 100%; animation: wave forwards ease-out; }}}Copy the code

The last

The above story is pure fiction, from the author’s open source project components, may not be the best way to write, welcome suggestions.

React -dark-photo react-dark-photo

Vue version source code: VUe-dark-photo

You see this? No thumbs up?