Javascript 如何在点击时创建波纹效果 - Material Design

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30074246/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 04:37:47  来源:igfitidea点击:

How to create Ripple effect on Click - Material Design

javascriptjqueryhtmlcss

提问by Antonin Cezard

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.

我是 CSS 动画的新手,在过去的几个小时里我一直试图通过查看他们的代码来使他们的动画工作,但我现在无法让它工作。

I'm talking about this effect: https://angular.io/(menu effect). Basically, it's an animation on click that spreads a circle from the mouse cursor.

我说的是这个效果:https: //angular.io/(菜单效果)。基本上,它是一个点击动画,从鼠标光标传播一个圆圈。

Seems it comes down to these 2 lines:

似乎归结为这两行:

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: Maybe there's some jQuery I didn't see.

PS:也许有一些我没有看到的 jQuery。

采纳答案by Ruddy

I have used this sort of code before on a few of my projects.

我以前在我的一些项目中使用过这种代码。

Using jQuery we can position the effect to its not just static and then we add the spanelement onclick. I have added comments so it makes it easier to follow.

使用 jQuery 我们可以将效果定位到它而不是静态,然后我们添加span元素onclick。我添加了评论,以便更容易理解。

Demo Here

演示在这里

jQuery

jQuery

$("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

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

回答by Roko C. Buljan

Ripple effect in Material Design using jQuery and CSS3

使用 jQuery 和 CSS3 的 Material Design 中的波纹效果

Click Ripple Google material design

点击涟漪谷歌材料设计

To create a UX Ripple effectbasically you need to:

要创建 UX Ripple 效果,基本上您需要:

  • appendto any element an oveflow:hiddenelement to contain the ripple circle(you don't want to alter your original element overflow, neither see the ripple effect go outside of a desired container)
  • appendto the overflow container the ripple wavetranslucent radial element
  • get the click coordinatesand CSS3 animatethe scaling and opacity of the ripple element
  • Listen for the animationendevent and destroy the ripple container.
  • 一个oveflow:hidden元素附加到任何元素以包含波纹圆圈(您不想改变原始元素溢出,也不想看到波纹效果超出所需容器)
  • 波纹波半透明径向元素 附加到溢出容器
  • 获取点击坐标CSS3 动画波纹元素的缩放和不透明度
  • 侦听animationend事件并销毁涟漪容器


The basic code:

基本代码:

Basically add data-ripple(default as white ripple) or data-ripple="#000"to a desired element:

基本上添加data-ripple(默认为白色波纹)或添加data-ripple="#000"到所需元素:

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

CSS:

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

jQuery

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

});


Here's a full-featured demo:

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

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>

回答by web-tiki

This can be achieved with box-shadows. The positioning of the circle origin under the mouse when clicked will need JS.

这可以通过框阴影来实现。点击时鼠标下圆原点的定位需要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>

回答by nu everest

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

这是一个 CSS - 仅实现,即不需要 javascript。

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

来源: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>

回答by nu everest

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.

您可以在Materialize css的帮助下获得相同的效果,使用它非常容易。你所要做的就是在你想要效果的地方添加一个类。

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

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

如果您想使用纯 CSS,请检查此代码笔:Ripple effect

回答by mladen.plavsic

You could use http://mladenplavsic.github.io/css-ripple-effect/Pure CSS solution

您可以使用http://mladenplavsic.github.io/css-ripple-effect/纯 CSS 解决方案

<link href="https://cdn.rawgit.com/mladenplavsic/css-ripple-effect/35c35541/dist/ripple.min.css" rel="stylesheet"/>

<button class="ripple">Click me</button>

回答by Mahmoud Mohamed Zakria

Here is Material Design button component "The wave effect" Done Using pure CSS3 and JavaScript no libraries no framework Material Design button component "The wave effect"

这是 Material Design 按钮组件“波浪效果” 使用纯 CSS3 和 JavaScript 完成 无库 无框架 Material Design 按钮组件“波浪效果”

https://codepen.io/Mahmoud-Zakaria/pen/NvbORQ

https://codepen.io/Mahmoud-Zakaria/pen/NvbORQ

HTML

HTML

 <div class="md" >Click</div>

CSS

CSS

@keyframes glow-out {
  30%,80% {
   transform: scale(7);
 }
  100% {
   opacity: 0;
 }
}

.md {
 --y: 0;
 --x: 0;
display: inline-block;
padding: 20px 70px;
text-align: center;
background-color: lightcoral;
margin: 5em;
position: relative;
overflow: hidden;
cursor: pointer;
border-radius: 4px;
color: white;
}


.is-clicked {
  content: '';
  position: absolute;
  top: calc(var(--y) * 1px);
  left: calc(var(--x) * 1px);
  width: 100px;
  height:100px;
  background: rgba(255, 255, 255, .3);
  border-radius: 50%;
  animation: glow-out 1s ease-in-out forwards;
  transform: translate(-50%, -50%);  
 }

JS

JS

// Material Design button Module 
 let md_module = (function() {

 let btn = document.querySelectorAll(".md");
 let md_btn = Array.prototype.slice.call(btn);

  md_btn.forEach(eachCB)

 function eachCB (item, index, array){

  function md(e) {
     let offsetX = e.clientX - item.offsetLeft;
     let offsetY = e.clientY - item.offsetTop;
     item.style.setProperty("--x", offsetX);
     item.style.setProperty("--y", offsetY);
     item.innerHTML += '<div class="is-clicked"></div>';
   }

function rm() {
  let state = item.querySelectorAll(".is-clicked");
  console.log(state)
  for (let i = 0; i < state.length; i++) {
    if (state[i].className === "is-clicked") {
      state[i].remove();
    }
  }
}

item.addEventListener("click", md);
item.addEventListener("animationend", rm);
}

 })();

回答by Thomas Dondorf

CSS Paint API (introduced in 2018)

CSS Paint API(2018 年推出)

The new CSS Paint API(part of the CSS "Houdini" draft) allows to write JavaScript functions to be used in CSS. Quote of the linked document:

新的CSS Paint API(CSS“Houdini”草案的一部分)允许编写 JavaScript 函数以在 CSS 中使用。引用链接文件:

CSS Paint API allows you to programmatically generate an image whenever a CSS property expects an image. Properties like background-imageor border-imageare usually used with url()to load an image file or with CSS built-in functions like linear-gradient(). Instead of using those, you can now use paint(myPainter)to reference a paint worklet.

CSS Paint API 允许您在 CSS 属性需要图像时以编程方式生成图像。像background-image或 之border-image类的属性通常用于url()加载图像文件或 CSS 内置函数,例如linear-gradient(). 您现在可以使用paint(myPainter)来引用绘制工作集,而不是使用它们。

This means you can implement a paint function in JavaScript and use it inside your CSS.

这意味着您可以在 JavaScript 中实现一个绘制函数并在您的 CSS 中使用它。

Browser support (May 2019)

浏览器支持(2019 年 5 月)

Currently, only Chrome and Opera support the Paint API of the Houdini draft. Firefox has signaled "intent to implement". See ishoudinireadyyet.comor caniuse.comfor more information.

目前,只有 Chrome 和 Opera 支持 Houdini 草稿的 Paint API。Firefox 已表示“有意实施”。有关更多信息,请访问 ishoudinireadyyet.comcaniuse.com

Code sample

代码示例

There is a working "ripple" example implemented by the Houdini task force available here. I extracted the "core" from the example below. It implements the custom paint function, adds custom CSS properties like --ripple-colorand uses a JavaScript function to implement the animation and to start and stop the effect.

有正常工作的“涟漪”例如通过霍迪尼专案组实施可以在这里找到。我从下面的示例中提取了“核心”。它实现了自定义绘制功能,添加了自定义 CSS 属性,例如--ripple-color并使用 JavaScript 函数来实现动画和开始和停止效果。

Note, that it adds the custom paint function like this:

请注意,它添加了这样的自定义绘制功能:

CSS.paintWorklet.addModule('https://googlechromelabs.github.io/houdini-samples/paint-worklet/ripple/paintworklet.js');

If you want to use the effect on your website, I recommend you download the file and reference it locally.

如果您想在您的网站上使用该效果,我建议您下载该文件并在本地引用。

// Adds the custom paint function
CSS.paintWorklet.addModule('https://googlechromelabs.github.io/houdini-samples/paint-worklet/ripple/paintworklet.js');

// the actual animation of the ripple effect
function rippleEffect(element) {
  let start, x, y;
  element.addEventListener('click', function (evt) {
    this.classList.add('animating');
    [x, y] = [evt.offsetX, evt.offsetY];
    start = performance.now();
    const raf = (now) => {
      const tick = Math.floor(now - start);
      this.style.cssText = `--ripple-x: ${x}; --ripple-y: ${y}; --animation-tick: ${tick};`;
      if (tick > 1000) {
        this.classList.remove('animating');
        this.style.cssText = `--animation-tick: 0`;
        return;
      }
      requestAnimationFrame(raf);
    };
    requestAnimationFrame(raf);
  });
}

rippleEffect(document.querySelector('.ripple'));
.ripple {
  font-size: 5em;
  background-color: rgb(0, 169, 244);

  /* custom property */
  --ripple-color: rgba(255, 255, 255, 0.54);
}

.ripple.animating {
  /* usage of the custom "ripple" paint function */
  background-image: paint(ripple);
}
<button class="ripple">Click me!</button>

回答by user220409

Realization javascript+ babel-

实现javascript+ babel-

javascript-

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-

css——

@keyframes impulse {
    from {
        opacity: .3;

        transform: scale(0);
    }
    to {
        opacity: 0;

        transform: scale(1);
    }
}

to use so -

使用这样 -

html-

html——

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

javascript-

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

生活示例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>

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

生活示例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>

回答by eflurdulis

Use canvas and animate a circle shape to scale up.

使用画布并为圆形设置动画以放大。