How to create Ripple effect on Click - Material Design

后端 未结 10 1512
南旧
南旧 2020-12-12 11:17

I\'m new to CSS animations and I\'ve been trying to make their animation work for the last hours by looking at their code, but I can\'t make it work for now.

I\'m ta

相关标签:
10条回答
  • 2020-12-12 12:09

    Use canvas and animate a circle shape to scale up.

    0 讨论(0)
  • 2020-12-12 12:10

    Realization javascript + babel -

    javascript -

    class ImpulseStyleFactory {
        static ANIMATION_DEFAULT_DURATION = 1;
        static ANIMATION_DEFAULT_SIZE = 300;
        static ANIMATION_RATIO = ImpulseStyleFactory.ANIMATION_DEFAULT_DURATION / ImpulseStyleFactory.ANIMATION_DEFAULT_SIZE;
    
        static circleImpulseStyle( x, y, size, color = `#fff`, duration = 1 ){
            return {
                width: `${ size }px`,
                height: `${ size }px`,
    
                background: color,
    
                borderRadius: `50%`,
    
                display: `inline-block`,
    
                pointerEvents: `none`,
    
                position: `absolute`,
    
                top: `${ y - size / 2 }px`,
                left: `${ x - size / 2 }px`,
    
                animation: `impulse ${ duration }s`,
            };
        }
    }
    
    
    class Impulse {
        static service = new Impulse();
    
    
        static install( container ) {
            Impulse.service.containerRegister( container );
        }
        static destroy( container ){
            Impulse.service.containerUnregister( container );
        }
    
        static applyToElement( {x, y}, container ){
            Impulse.service.createImpulse( x, y, container );
        }
    
        constructor(){
            this.impulse_clickHandler = this.impulse_clickHandler.bind(this);
            this.impulse_animationEndHandler = this.impulse_animationEndHandler.bind(this);
    
            this.actives = new Map();
        }
    
        containerRegister( container ){
            container.addEventListener('click', this.impulse_clickHandler);
        }
        containerUnregister( container ){
            container.removeEventListener('click', this.impulse_clickHandler);
        }
    
        createImpulse( x, y, container ){
            let { clientWidth, clientHeight } = container;
    
            let impulse = document.createElement('div');
            impulse.addEventListener('animationend', this.impulse_animationEndHandler);
    
            let size = Math.max( clientWidth, clientHeight ) * 2;
            let color = container.dataset.color;
    
            Object.assign(impulse.style, ImpulseStyleFactory.circleImpulseStyle(
                x, y, size, color
            ));
    
            if( this.actives.has( container ) ){
                this.actives.get( container )
                            .add( impulse );
            }else{
                this.actives.set( container, new Set( [ impulse ] ) );
            }
    
            container.dataset.active = true;
    
            container.appendChild( impulse );
        }
    
    
        impulse_clickHandler({ layerX, layerY, currentTarget: container }){
            this.createImpulse( layerX, layerY, container );        
        }
    
        impulse_animationEndHandler( {currentTarget: impulse} ){
            let { parentNode: container  } = impulse;
    
            this.actives.get( container )
                        .delete( impulse );
    
            if( ! this.actives.get( container ).size ){
                this.actives.delete( container );
    
                container.dataset.active = false;
            }
    
            container.removeChild(impulse);
        }
    }
    

    css -

    @keyframes impulse {
        from {
            opacity: .3;
    
            transform: scale(0);
        }
        to {
            opacity: 0;
    
            transform: scale(1);
        }
    }
    

    to use so -

    html -

    <div class="impulse" data-color="#3f1dcb" data-active="false">
        <div class="panel"></div>
    </div>
    

    javascript -

    let impulses = document.querySelectorAll('.impulse');
    let impulseAll = Array.from( impulses );
    
    impulseAll.forEach( Impulse.install );
    

    Life example Impulse.install ( impulse create in coords of click, add handler event click ) -

    class ImpulseStyleFactory {
        static ANIMATION_DEFAULT_DURATION = 1;
        static ANIMATION_DEFAULT_SIZE = 300;
        static ANIMATION_RATIO = ImpulseStyleFactory.ANIMATION_DEFAULT_DURATION / ImpulseStyleFactory.ANIMATION_DEFAULT_SIZE;
    
        static circleImpulseStyle( x, y, size, color = `#fff`, duration = 1 ){
            return {
                width: `${ size }px`,
                height: `${ size }px`,
    
                background: color,
    
                borderRadius: `50%`,
    
                display: `inline-block`,
    
                pointerEvents: `none`,
    
                position: `absolute`,
    
                top: `${ y - size / 2 }px`,
                left: `${ x - size / 2 }px`,
    
                animation: `impulse ${ duration }s`,
            };
        }
    }
    
    
    class Impulse {
        static service = new Impulse();
    
    
        static install( container ) {
            Impulse.service.containerRegister( container );
        }
        static destroy( container ){
            Impulse.service.containerUnregister( container );
        }
    
        static applyToElement( {x, y}, container ){
            Impulse.service.createImpulse( x, y, container );
        }
    
        constructor(){
            this.impulse_clickHandler = this.impulse_clickHandler.bind(this);
            this.impulse_animationEndHandler = this.impulse_animationEndHandler.bind(this);
    
            this.actives = new Map();
        }
    
        containerRegister( container ){
            container.addEventListener('click', this.impulse_clickHandler);
        }
        containerUnregister( container ){
            container.removeEventListener('click', this.impulse_clickHandler);
        }
    
        createImpulse( x, y, container ){
            let { clientWidth, clientHeight } = container;
    
            let impulse = document.createElement('div');
            impulse.addEventListener('animationend', this.impulse_animationEndHandler);
    
            let size = Math.max( clientWidth, clientHeight ) * 2;
            let color = container.dataset.color;
    
            Object.assign(impulse.style, ImpulseStyleFactory.circleImpulseStyle(
                x, y, size, color
            ));
    
            if( this.actives.has( container ) ){
                this.actives.get( container )
                    .add( impulse );
            }else{
                this.actives.set( container, new Set( [ impulse ] ) );
            }
    
            container.dataset.active = true;
    
            container.appendChild( impulse );
        }
    
    
        impulse_clickHandler({ layerX, layerY, currentTarget: container }){
            this.createImpulse( layerX, layerY, container );
        }
    
        impulse_animationEndHandler( {currentTarget: impulse} ){
            let { parentNode: container  } = impulse;
    
            this.actives.get( container )
                .delete( impulse );
    
            if( ! this.actives.get( container ).size ){
                this.actives.delete( container );
    
                container.dataset.active = false;
            }
    
            container.removeChild(impulse);
        }
    }
    
    
    
    let impulses = document.querySelectorAll('.impulse');
    let impulseAll = Array.from( impulses );
    
    impulseAll.forEach( Impulse.install );
    @import "https://cdnjs.cloudflare.com/ajax/libs/normalize/6.0.0/normalize.min.css";
    /*@import url('https://fonts.googleapis.com/css?family=Roboto+Mono');*/
    
    * {
        box-sizing: border-box;
    }
    
    html {
        font-family: 'Roboto Mono', monospace;
    }
    
    body {
        width: 100%;
        height: 100%;
    
        margin: 0;
    
        position: absolute;
    
    
    }
    
    main {
        width: 100%;
        height: 100%;
    
        overflow: hidden;
    
        position: relative;
    }
    
    
    .container {
        position: absolute;
    
        top: 0;
        left: 0;
    }
    
    .centred {
        display: flex;
    
        justify-content: center;
    
        align-items: center;
    }
    
    .shadow-xs {
        box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px;
    }
    .sample-impulse {
        transition: all .5s;
    
        overflow: hidden;
    
        position: relative;
    }
    .sample-impulse[data-active="true"] {
        box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px;
    }
    
    
    
    .panel {
        width: 300px;
        height: 100px;
    
        background: #fff;
    }
    
    
    .panel__hidden-label {
        color: #fff;
    
        font-size: 2rem;
        font-weight: bold;
    
        pointer-events: none;
    
        z-index: 1;
    
        position: absolute;
    }
    .panel__default-label {
        pointer-events: none;
    
        z-index: 2;
    
        position: absolute;
    }
    
    .sample-impulse[data-active="true"] .panel__default-label {
        display: none;
    }
    
    
    
    @keyframes impulse {
        from {
            opacity: .3;
    
            transform: scale(0);
        }
        to {
            opacity: 0;
    
            transform: scale(1);
        }
    }
    <main class="centred">
        <div class="sample-impulse impulse centred shadow-xs" data-color="#3f1dcb" data-active="false">
            <div class="group centred">
                <div class="panel"></div>
                <span class="panel__hidden-label">StackOverflow</span>
                <span class="panel__default-label">click me</span>
            </div>
        </div>
    </main>

    Life example Impulse.applyToElement ( impulse coords setby user, not add handler event click ) -

    class ImpulseStyleFactory {
        static ANIMATION_DEFAULT_DURATION = 1;
        static ANIMATION_DEFAULT_SIZE = 300;
        static ANIMATION_RATIO = ImpulseStyleFactory.ANIMATION_DEFAULT_DURATION / ImpulseStyleFactory.ANIMATION_DEFAULT_SIZE;
    
        static circleImpulseStyle( x, y, size, color = `#fff`, duration = 1 ){
            return {
                width: `${ size }px`,
                height: `${ size }px`,
    
                background: color,
    
                borderRadius: `50%`,
    
                display: `inline-block`,
    
                pointerEvents: `none`,
    
                position: `absolute`,
    
                top: `${ y - size / 2 }px`,
                left: `${ x - size / 2 }px`,
    
                animation: `impulse ${ duration }s`,
            };
        }
    }
    
    
    class Impulse {
        static service = new Impulse();
    
    
        static install( container ) {
            Impulse.service.containerRegister( container );
        }
        static destroy( container ){
            Impulse.service.containerUnregister( container );
        }
    
        static applyToElement( {x, y}, container ){
            Impulse.service.createImpulse( x, y, container );
        }
    
        constructor(){
            this.impulse_clickHandler = this.impulse_clickHandler.bind(this);
            this.impulse_animationEndHandler = this.impulse_animationEndHandler.bind(this);
    
            this.actives = new Map();
        }
    
        containerRegister( container ){
            container.addEventListener('click', this.impulse_clickHandler);
        }
        containerUnregister( container ){
            container.removeEventListener('click', this.impulse_clickHandler);
        }
    
        createImpulse( x, y, container ){
            let { clientWidth, clientHeight } = container;
    
            let impulse = document.createElement('div');
            impulse.addEventListener('animationend', this.impulse_animationEndHandler);
    
            let size = Math.max( clientWidth, clientHeight ) * 2;
            let color = container.dataset.color;
    
            Object.assign(impulse.style, ImpulseStyleFactory.circleImpulseStyle(
                x, y, size, color
            ));
    
            if( this.actives.has( container ) ){
                this.actives.get( container )
                    .add( impulse );
            }else{
                this.actives.set( container, new Set( [ impulse ] ) );
            }
    
            container.dataset.active = true;
    
            container.appendChild( impulse );
        }
    
    
        impulse_clickHandler({ layerX, layerY, currentTarget: container }){
            this.createImpulse( layerX, layerY, container );
        }
    
        impulse_animationEndHandler( {currentTarget: impulse} ){
            let { parentNode: container  } = impulse;
    
            this.actives.get( container )
                .delete( impulse );
    
            if( ! this.actives.get( container ).size ){
                this.actives.delete( container );
    
                container.dataset.active = false;
            }
    
            container.removeChild(impulse);
        }
    }
    
    
    
    const generateRandomPointByRectdAll = ( { width, height }, length = 1 ) => {
        let result = [];
    
        while( length-- ){
            result.push( {
                x: Math.round( Math.random() * width ),
                y: Math.round( Math.random() * height )
            } );
        }
    
        return result;
    };
    
    const delayTask = ( task, delay ) => new Promise( ( resolve, reject ) => {
        let timeoutID = setTimeout( () => task( ), delay )
    } );
    
    document.addEventListener( 'click', () => {
        const MAX_IMPULSE_DELAY_TIME = 5000;
    
        let container = document.querySelector('.custom-impulse');
        let pointAll = generateRandomPointByRectdAll( {
            width: container.clientWidth,
            height: container.clientHeight
        }, 5 );
        let taskAll = pointAll.map( point => () => Impulse.applyToElement( point, container ) );
        let delayTaskAll = taskAll.map( task => delayTask( task, Math.round( Math.random() * MAX_IMPULSE_DELAY_TIME ) ) );
    } );
    @import "https://cdnjs.cloudflare.com/ajax/libs/normalize/6.0.0/normalize.min.css";
    /*@import url('https://fonts.googleapis.com/css?family=Roboto+Mono');*/
    
    * {
        box-sizing: border-box;
    }
    
    html {
        font-family: 'Roboto Mono', monospace;
    }
    
    body {
        width: 100%;
        height: 100%;
    
        margin: 0;
    
        position: absolute;
    
    
    }
    
    main {
        width: 100%;
        height: 100%;
    
        overflow: hidden;
    
        position: relative;
    }
    
    .container-fill {
        width: 100%;
        height: 100%;
    }
    
    .container {
        position: absolute;
    
        top: 0;
        left: 0;
    }
    
    .centred {
        display: flex;
    
        justify-content: center;
    
        align-items: center;
    }
    
    
    .custom-impulse {
        will-change: transform, opasity;
    
        position: absolute;
    }
    
    
    @keyframes impulse {
        from {
            opacity: .3;
    
            transform: scale(0);
        }
        to {
            opacity: 0;
    
            transform: scale(1);
        }
    }
    <main class="centred">
        <div class="custom-impulse container-fill centred" data-color="#3f1dcb" data-active="false">
            <span>click me</span>
        </div>
    </main>

    0 讨论(0)
  • 2020-12-12 12:15

    Here is a CSS - only implementation i.e. no javascript required.

    Source: https://ghinda.net/article/css-ripple-material-design/

    body {
      background: #fff;
    }
    
    button {
      position: relative;
      overflow: hidden;
      padding: 16px 32px;
    }
    
    button:after {
      content: '';
      display: block;
      position: absolute;
      left: 50%;
      top: 50%;
      width: 120px;
      height: 120px;
      margin-left: -60px;
      margin-top: -60px;
      background: #3f51b5;
      border-radius: 100%;
      opacity: .6;
    
      transform: scale(0);
    }
    
    @keyframes ripple {
      0% {
        transform: scale(0);
      }
      20% {
        transform: scale(1);
      }
      100% {
        opacity: 0;
        transform: scale(1);
      }
    }
    
    button:not(:active):after {
      animation: ripple 1s ease-out;
    }
    
    /* fixes initial animation run, without user input, on page load.
     */
    button:after {
      visibility: hidden;
    }
    
    button:focus:after {
      visibility: visible;
    }
    <button>
      Button
    </button>

    0 讨论(0)
  • 2020-12-12 12:15

    You can get the same effect with the help of Materialize css, making it with that is quite easy. All you have to do is just add a class to where you want the effect.

    <a href="#" class="btn waves-effect waves-light">Submit</a> 
    

    If you want to go with pure CSS check this codepen it : Ripple effect

    0 讨论(0)
提交回复
热议问题