RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 653854
Accepted
Alexandr_TT
Alexandr_TT
Asked:2020-04-15 01:40:06 +0000 UTC2020-04-15 01:40:06 +0000 UTC 2020-04-15 01:40:06 +0000 UTC

如何在点击时创建涟漪效果 - `Material Design`

  • 772

我是 CSS 动画的新手,最近几个小时一直在尝试让动画正常工作。试图理解代码Material Design,但还不能让它工作。

我说的是这个效果:https ://angular.io/ (菜单效果)。基本上,这是一个点击动画,从鼠标光标开始呈圆形展开。

似乎可以归结为这两行:

transition: box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1),-webkit-transform .4s cubic-bezier(.25,.8,.25,1);
transition: box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1),transform .4s cubic-bezier(.25,.8,.25,1);   

PS:也许有一些代码jQuery实现了这个动画。

免费翻译问题How to create Ripple effect on Click - Material Design by @Antonin Cezard。

javascript
  • 5 5 个回答
  • 10 Views

5 个回答

  • Voted
  1. Best Answer
    Sasha Omelchenko
    2020-04-15T04:01:43Z2020-04-15T04:01:43Z

    Вариант с CSS-переменными, которые используются для раздельного использования свойства transform. Разделять это свойство понадобилось для того, чтобы правильно перемещать элемент (translate3d) и независимо от перемещения масштабировать (scale). Через JS так можно очень удобно управлять этими параметрами.

    С точки зрения производительности это достаточно хорошая анимация такого эффекта, поскольку не делаются вставки и удаления узлов в DOM и вся работа происходит исключительно со свойствами transform и opacity, которые не влияют на композитный слой, а значит не вызывают множество перерисовок страницы.

    let span = document.querySelector('button span');
    
    document.querySelector('button').addEventListener('click', function(e) {
      span.style.setProperty('--x', e.clientX - this.getBoundingClientRect().left - span.offsetWidth/2 + 'px');
      span.style.setProperty('--y', e.clientY - this.getBoundingClientRect().top - span.offsetHeight/2 + 'px');
      
      let scaleCount = 0,
          opacityCount = 1;
          
      const animationTime = 500;
    
      let scaleUp = setInterval(function() {
        scaleCount += 0.25;
        span.style.setProperty('--scale', scaleCount);
        
        opacityCount -= 0.05;
        span.style.opacity = opacityCount;
      }, animationTime / 20);
    
      setTimeout(function() {
        clearInterval(scaleUp);
        span.style.setProperty('--scale', 0);
      }, animationTime);
    });
    body {
      --x: 0px;
      --y: 0px;
      --scale: 0;
    }
    
    button {
      padding: 10px 20px;
      position: relative;
      overflow: hidden;
      border: 0;
      border-radius: 3px;
      background: linear-gradient(#c4e2fa, #c4effa);
      outline: 0;
      font: bold 17px arial;
      text-shadow: 0 0 3px rgba(0,0,0,0.3);
      color: #FFF;
    }
    
    button:hover {
      background: linear-gradient(#c4e2fa, #c4e8fa);
    }
    
    span {
      position: absolute;
      width: 30px;
      height: 30px;
      border-radius: 50%;
      background-color: rgba(0, 0, 0, 0.3);
      top: 0;
      left: 0;
      transform: translate3d(var(--x), var(--y), 0) scale(var(--scale));
    }
    <button>This is text<span></span></button>

    • 12
  2. Alexandr_TT
    2020-04-15T02:19:11Z2020-04-15T02:19:11Z

    使用 jQuery 和 CSS3 在 Material Design 中实现波纹效果

    jsBin演示

    在此处输入图像描述

    要创建UI 波纹效果,您需要:

    • 附加到任何元素oveflow:hidden以限制波纹圈(因为您不想更改原始元素,所以使用overflow不会看到波纹效果超出所需容器)
    • 向容器 c添加overflow半透明径向元素ripple wave
    • 获取鼠标点击坐标,并用于CSS3动画缩放和不透明度ripple element
    • 听事件 - 动画并破坏涟漪。

    主要代码:

    基本上添加data-ripple(默认为白色波纹)或data-ripple="# 000"为您想要的元素:

    <a data-ripple> EDIT </a>
    <div data-ripple="rgba(0,0,0, 0.3)">Lorem ipsum</div>      
    

    CSS:

    /* MAD-RIPPLE EFFECT */
    .ripple{
      position: absolute;
      top:0; left:0; bottom:0; right:0;
      overflow: hidden;
      -webkit-transform: translateZ(0); /* to contain zoomed ripple */
      transform: translateZ(0);
      border-radius: inherit; /* inherit from parent (rounded buttons etc) */
      pointer-events: none; /* allow user interaction */
              animation: ripple-shadow 0.4s forwards;
      -webkit-animation: ripple-shadow 0.4s forwards;
    }
    .rippleWave{
      backface-visibility: hidden;
      position: absolute;
      border-radius: 50%;
      transform: scale(0.7); -webkit-transform: scale(0.7);
      background: rgba(255,255,255, 1);
      opacity: 0.45;
              animation: ripple 2s forwards;
      -webkit-animation: ripple 2s forwards;
    }
    @keyframes ripple-shadow {
      0%   {box-shadow: 0 0 0 rgba(0,0,0,0.0);}
      20%  {box-shadow: 0 4px 16px rgba(0,0,0,0.3);}
      100% {box-shadow: 0 0 0 rgba(0,0,0,0.0);}
    }
    @-webkit-keyframes ripple-shadow {
      0%   {box-shadow: 0 0 0 rgba(0,0,0,0.0);}
      20%  {box-shadow: 0 4px 16px rgba(0,0,0,0.3);}
      100% {box-shadow: 0 0 0 rgba(0,0,0,0.0);}
    }
    @keyframes ripple {
      to {transform: scale(24); opacity:0;}
    }
    @-webkit-keyframes ripple {
      to {-webkit-transform: scale(24); opacity:0;}
    }   
    

    查询

    jQuery(function($) {
    
      // MAD-RIPPLE // (jQ+CSS)
      $(document).on("mousedown", "[data-ripple]", function(e) {
    
        var $self = $(this);
    
        if($self.is(".btn-disabled")) {
          return;
        }
        if($self.closest("[data-ripple]")) {
          e.stopPropagation();
        }
    
        var initPos = $self.css("position"),
            offs = $self.offset(),
            x = e.pageX - offs.left,
            y = e.pageY - offs.top,
            dia = Math.min(this.offsetHeight, this.offsetWidth, 100), // start diameter
            $ripple = $('<div/>', {class : "ripple",appendTo : $self });
    
        if(!initPos || initPos==="static") {
          $self.css({position:"relative"});
        }
    
        $('<div/>', {
          class : "rippleWave",
          css : {
            background: $self.data("ripple"),
            width: dia,
            height: dia,
            left: x - (dia/2),
            top: y - (dia/2),
          },
          appendTo : $ripple,
          one : {
            animationend : function(){
              $ripple.remove();
            }
          }
        });
      });
    
    });    
    

    这是一个功能齐全的演示:

    jQuery(function($) {
    
      // MAD-RIPPLE // (jQ+CSS)
      $(document).on("mousedown", "[data-ripple]", function(e) {
        
        var $self = $(this);
        
        if($self.is(".btn-disabled")) {
          return;
        }
        if($self.closest("[data-ripple]")) {
          e.stopPropagation();
        }
        
        var initPos = $self.css("position"),
            offs = $self.offset(),
            x = e.pageX - offs.left,
            y = e.pageY - offs.top,
            dia = Math.min(this.offsetHeight, this.offsetWidth, 100), // start diameter
            $ripple = $('<div/>', {class : "ripple",appendTo : $self });
        
        if(!initPos || initPos==="static") {
          $self.css({position:"relative"});
        }
        
        $('<div/>', {
          class : "rippleWave",
          css : {
            background: $self.data("ripple"),
            width: dia,
            height: dia,
            left: x - (dia/2),
            top: y - (dia/2),
          },
          appendTo : $ripple,
          one : {
            animationend : function(){
              $ripple.remove();
            }
          }
        });
      });
    
    });
    *{box-sizing:border-box; -webkit-box-sizing:border-box;}
    html, body{height:100%; margin:0;}
    body{background:#f5f5f5; font: 14px/20px Roboto, sans-serif;}
    h1, h2{font-weight: 300;}
    
    
    /* MAD-RIPPLE EFFECT */
    .ripple{
      position: absolute;
      top:0; left:0; bottom:0; right:0;
      overflow: hidden;
      -webkit-transform: translateZ(0); /* to contain zoomed ripple */
      transform: translateZ(0);
      border-radius: inherit; /* inherit from parent (rounded buttons etc) */
      pointer-events: none; /* allow user interaction */
              animation: ripple-shadow 0.4s forwards;
      -webkit-animation: ripple-shadow 0.4s forwards;
    }
    .rippleWave{
      backface-visibility: hidden;
      position: absolute;
      border-radius: 50%;
      transform: scale(0.7); -webkit-transform: scale(0.7);
      background: rgba(255,255,255, 1);
      opacity: 0.45;
              animation: ripple 2s forwards;
      -webkit-animation: ripple 2s forwards;
    }
    @keyframes ripple-shadow {
      0%   {box-shadow: 0 0 0 rgba(0,0,0,0.0);}
      20%  {box-shadow: 0 4px 16px rgba(0,0,0,0.3);}
      100% {box-shadow: 0 0 0 rgba(0,0,0,0.0);}
    }
    @-webkit-keyframes ripple-shadow {
      0%   {box-shadow: 0 0 0 rgba(0,0,0,0.0);}
      20%  {box-shadow: 0 4px 16px rgba(0,0,0,0.3);}
      100% {box-shadow: 0 0 0 rgba(0,0,0,0.0);}
    }
    @keyframes ripple {
      to {transform: scale(24); opacity:0;}
    }
    @-webkit-keyframes ripple {
      to {-webkit-transform: scale(24); opacity:0;}
    }
    
    
    /* MAD-BUTTONS (demo) */
    [class*=mad-button-]{
      display:inline-block;
      text-align:center;
      position: relative;
      margin: 0;
      white-space: nowrap;
      vertical-align: middle;
      font-family: "Roboto", sans-serif;
      font-size: 14px;
      font-weight: 500;
      text-transform: uppercase;
      text-decoration: none;
      border: 0; outline: 0;
      background: none;
      transition: 0.3s;
      cursor: pointer;
      color: rgba(0,0,0, 0.82);
    }
    [class*=mad-button-] i.material-icons{
      vertical-align:middle;
      padding:0;
    }
    .mad-button-raised{
      height: 36px;
      padding: 0px 16px;
      line-height: 36px;
      border-radius: 2px;
      box-shadow: /*amb*/ 0 0   2px rgba(0,0,0,0.15),
        /*key*/ 0 1px 3px rgba(0,0,0,0.25);
    }.mad-button-raised:hover{
      box-shadow: /*amb*/ 0 0   2px rgba(0,0,0,0.13),
        /*key*/ 0 2px 4px rgba(0,0,0,0.2);
    }
    .mad-button-action{
      width: 56px; height:56px;
      padding: 16px 0;
      border-radius: 32px;
      box-shadow: /*amb*/ 0 0   2px rgba(0,0,0,0.13),
        /*key*/ 0 5px 7px rgba(0,0,0,0.2);
    }.mad-button-action:hover{
      box-shadow: /*amb*/ 0 0   2px rgba(0,0,0,0.11),
        /*key*/ 0 6px 9px rgba(0,0,0,0.18);
    }
    [class*=mad-button-].mad-ico-left  i.material-icons{ margin: 0 8px 0 -4px; }
    [class*=mad-button-].mad-ico-right i.material-icons{ margin: 0 -4px 0 8px; }
    
    /* MAD-COLORS */
    .bg-primary-darker{background:#1976D2; color:#fff;}
    .bg-primary{ background:#2196F3; color:#fff; }
    .bg-primary.lighter{ background: #BBDEFB; color: rgba(0,0,0,0.82);}
    .bg-accented{ background:#FF4081; color:#fff; }
    
    /* MAD-CELL */
    .cell{padding: 8px 16px; overflow:auto;}
    <link href='https://fonts.googleapis.com/css?family=Roboto:500,400,300&amp;subset=latin,latin-ext' rel='stylesheet' type='text/css'>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-2.1.4.js"></script>
    
    <div class="cell">
      <button data-ripple class="mad-button-raised mad-ico-left bg-primary"><i class="material-icons">person</i>User settings</button>
      <a data-ripple href="#" class="mad-button-action bg-accented"><i class="material-icons">search</i></a>
    </div>
    
    <div data-ripple class="cell bg-primary-darker">
      <h1>Click to Ripple</h1>
      <p>data-ripple</p>
    </div>
    
    <div data-ripple="rgba(0,0,0, 0.4)" class="cell bg-primary">
      <p>data-ripple="rgba(0,0,0, 0.4)"</p>
      <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore....</p>
      <p><a data-ripple class="mad-button-raised mad-ico-right bg-accented">Edit<i class="material-icons">edit</i></a></p>
    </div>

    来自 @andRoko C. Buljan的答案的松散翻译 。

    • 7
  3. Alexandr_TT
    2020-04-15T01:40:06Z2020-04-15T01:40:06Z

    我以前在我的几个项目中使用过这段代码。

    使用jQuery,我们不仅可以静态地对其施加影响,还可以将其添加到元素span onclick。

    我添加了注释以便更容易理解代码。

    演示点这里

    查询

    $("div").click(function (e) {
    
      // Remove any old one
      $(".ripple").remove();
    
      // Setup
      var posX = $(this).offset().left,
          posY = $(this).offset().top,
          buttonWidth = $(this).width(),
          buttonHeight =  $(this).height();
    
      // Add the element
      $(this).prepend("<span class='ripple'></span>");
    
    
     // Make it round!
      if(buttonWidth >= buttonHeight) {
        buttonHeight = buttonWidth;
      } else {
        buttonWidth = buttonHeight; 
      }
    
      // Get the center of the element
      var x = e.pageX - posX - buttonWidth / 2;
      var y = e.pageY - posY - buttonHeight / 2;
    
    
      // Add the ripples CSS and start the animation
      $(".ripple").css({
        width: buttonWidth,
        height: buttonHeight,
        top: y + 'px',
        left: x + 'px'
      }).addClass("rippleEffect");
    });   
    

    CSS

    .ripple {
      width: 0;
      height: 0;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.4);
      transform: scale(0);
      position: absolute;
      opacity: 1;
    }
    .rippleEffect {
        animation: rippleDrop .6s linear;
    }
    
    @keyframes rippleDrop {
      100% {
        transform: scale(2);
        opacity: 0;
      }
    }   
    

    来自成员 @Ruddy的答案的松散翻译 。

    • 5
  4. user220409
    2020-04-17T03:44:01Z2020-04-17T03:44:01Z

    На чистом js + 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);
        }
    }
    

    использовать так -

    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 );
    

    Живой пример с Impulse.install ( импульс создается в координатах клика, навешивается слушатель события 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>

    现场示例Impulse.applyToElement(手动设置动量坐标,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>

    • 5
  5. Alexandr_TT
    2020-04-15T01:43:42Z2020-04-15T01:43:42Z

    这样的动画可以用box-shadows. 将圆圈的起点放在鼠标光标下,单击鼠标时需要JS.

    li{
        font-size:2em;
        background:rgba(51, 51, 254, 0.8);
        list-style-type:none;
        display:inline-block;
        line-height:2em;
        width:6em;
        text-align:center;
        color:#fff;
        position:relative;
        overflow:hidden;
    }
    a{color:#fff;}
    a:after{
        content:'';
        position:absolute;
        border-radius:50%;
        height:10em; width:10em;
        top: -4em; left:-2em;
        box-shadow: inset 0 0 0 5em rgba(255,255,255,0.2);
        transition: box-shadow 0.8s;
    }
    a:focus:after{
        box-shadow: inset 0 0 0 0em rgba(255,255,255,0.2);
    }
    <ul>
        <li><a href="#">button</a></li>
    </ul>

    来自 @web-tiki贡献者的答案的松散翻译 。

    • 3

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5