如何在Click - Material Design上创建Ripple效果

时间:2015-05-06 10:33:47

标签: javascript jquery html css

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

我正在谈论这种效果: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。

10 个答案:

答案 0 :(得分:46)

使用jQuery和CSS3

在Material Design中产生连锁反应

Click Ripple Google material design

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

  • 追加oveflow:hidden元素的任何元素包含涟漪圈(您不想改变原始元素溢出,也不会看到涟漪效果超出了所需的容器)
  • 追加到溢出容器波纹波半透明径向元素
  • 获取点击坐标 CSS3动画涟漪元素的缩放和不透明度
  • 聆听animationend事件,摧毁涟漪容器

基本代码:

基本上将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

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>

答案 1 :(得分:45)

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

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

Demo Here

<强>的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

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

答案 2 :(得分:22)

这可以通过盒阴影来实现。单击鼠标时圆形原点的定位需要JS。

&#13;
&#13;
//here is a jquery cdn link : 
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script> 
<script>
$(document).ready(function(){
    $.('check_date.php',{},function(serverRespond){
       jsonRespond = JSON.parse(serverRespond);
       if(jsonRespond.status === 1){
          setTimeout(function(){
            // here is your js function that you want to execute 
          },afterMilSeconds);

       }
    });
});
</script>
&#13;
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);
}
&#13;
&#13;
&#13;

答案 3 :(得分:9)

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

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

&#13;
&#13;
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;
}
&#13;
<button>
  Button
</button>
&#13;
&#13;
&#13;

答案 4 :(得分:5)

你可以在Materialize css的帮助下获得同样的效果,这样做很容易。您所要做的就是将类添加到您想要效果的位置。

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

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

答案 5 :(得分:3)

您可以使用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>

答案 6 :(得分:3)

这是材质设计按钮组件&#34;波浪效果&#34;完成使用纯CSS3和JavaScript没有库没有框架 材质设计按钮组件&#34;波浪效果&#34;

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

HTML

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

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

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

 })();

答案 7 :(得分:0)

CSS Paint API(于2018年推出)

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

  

CSS Paint API使您可以在CSS属性需要图像时以编程方式生成图像。像background-imageborder-image这样的属性通常与url()一起使用来加载图像文件,或者与linear-gradient()这样的CSS内置函数一起使用。现在,您可以使用paint(myPainter)来引用绘画工作集,而不必使用它们。

这意味着您可以在JavaScript中实现绘画功能,并在CSS中使用它。

浏览器支持(2019年5月)

当前,只有Chrome和Opera支持Houdini草稿的Paint API。 Firefox已发出“实施意向”。有关更多信息,请参见ishoudinireadyyet.comcaniuse.com

代码示例

Houdini工作队available here实现了一个有效的“涟漪”示例。我从下面的示例中提取了“核心”。它实现了自定义绘画功能,添加了--ripple-color之类的自定义CSS属性,并使用JavaScript函数来实现动画并开始和停止效果。

请注意,它添加了如下的自定义绘画功能:

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

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

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

答案 8 :(得分:-1)

实现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);
    }
}

使用 -

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

&#13;
&#13;
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 );
&#13;
@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);
    }
}
&#13;
<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>
&#13;
&#13;
&#13;

生活示例Impulse.applyToElement(用户设置的冲动坐标,不添加处理程序事件click) -

&#13;
&#13;
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 ) ) );
} );
&#13;
@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);
    }
}
&#13;
<main class="centred">
    <div class="custom-impulse container-fill centred" data-color="#3f1dcb" data-active="false">
        <span>click me</span>
    </div>
</main>
&#13;
&#13;
&#13;

答案 9 :(得分:-1)

使用画布并为圆形添加动画效果。