【问题标题】:How to create Ripple effect on Click - Material Design如何在点击时创建波纹效果 - Material Design
【发布时间】:2015-07-16 10:37:42
【问题描述】:

我是 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。

【问题讨论】:

标签: javascript jquery html css


【解决方案1】:

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

要创建用户体验涟漪效应,基本上您需要:

  • 追加到任何元素 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>

【讨论】:

  • 嗨...这看起来很棒,但是在尝试实现它时给了我一些问题。有没有机会我可以联系你并得到你的帮助?喜欢通过电子邮件或环聊或其他方式?提前致谢
  • width: h .. 你确定吗?可怜的w 感到孤独和无用。
  • 谢谢!! wh 用于确定理想的起始波纹直径。编辑以改进并再次感谢您的关注。 @Cooleronie
  • @RokoC.Buljan 没问题!谢谢你的sn-p :) 补充一下,实际的ink ripples 也有一个elevation 的效果来轻轻抬起元素。 See here, in the ink ripple video。由于您按照材料设计原则列出了这个 sn-p,我建议也添加这个效果。
【解决方案2】:

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

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

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

【讨论】:

  • hmm...杀死之前的涟漪完全是material精神。一旦需要,我会删除之前的涟漪。
  • 对于这个方案,我选择在动画完成后1ms去除波纹。
  • 如何避免没有任何溢出属性的涟漪元素溢出?
  • @everlasto 在这个我不是,它会溢出。恰好背景与波纹混合在一起。如果您想查看溢出版本,我还有另一个演示 here
  • 演示链接不适用于多个按钮,因为波纹效果总是粘在最上角。
【解决方案3】:

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

【讨论】:

  • 这只适用一次。用户经常点击按钮怎么样!
  • True @AlauddinAhmed ,动画在:focus 上启动,所以它只会工作一次,除非用户在按钮外点击
  • 这并没有回答所问的问题,详细说明如下:它是一个点击动画,从鼠标光标处展开一个圆圈
  • @MrUpsidown 正如答案中所说,这是部分解决方案。我仍然认为它是相关的,因为它不需要 JS 并且接近 OP 正在寻找的内容。
【解决方案4】:

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

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

【讨论】:

  • 我可以使用简单的 svg 吗?
  • 非常聪明! :不活跃)!这是关键
【解决方案5】:

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

你可以在Materialize css的帮助下得到同样的效果,用它做起来很容易。您所要做的就是在您想要效果的地方添加一个类。

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

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

【讨论】:

  • 还有js在涟漪效应中。
  • 这很好用,但显然涉及到 JavaScript。
  • 令人震惊的是,codepen 链接在出现问题时却是“材料设计涟漪”的最佳结果。
【解决方案7】:

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

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

 })();

【讨论】:

  • 多次点击时动画堆栈。
【解决方案8】:

CSS Paint API(2018 年推出)

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

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

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

浏览器支持(2019 年 5 月)

目前,只有 Chrome 和 Opera 支持 Houdini 草案的 Paint API。 Firefox 已发出“打算实施”的信号。请参阅ishoudinireadyyet.comcaniuse.com 了解更多信息。

代码示例

Houdini 工作组available here 实现了一个有效的“涟漪”示例。我从下面的示例中提取了“核心”。它实现了自定义绘制函数,添加了自定义 CSS 属性,如 --ripple-color,并使用 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);
}
&lt;button class="ripple"&gt;Click me!&lt;/button&gt;

【讨论】:

    【解决方案9】:

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

    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>

    【讨论】:

    • display: 'inline-block' 是不必要的,因为元素是position: 'absolute'
    【解决方案10】:

    您可以使用Tronic247 Material 框架来制作涟漪效果。

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>A Basic HTML5 Template</title>
      <link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
      <link href="https://cdn.jsdelivr.net/gh/tronic247/material/dist/css/material.min.css" rel="stylesheet" />
    </head>
    <body class="container">
        
        <div class="background-light-grey elevation-4 ripple-e dark-ripple" style="height: 200px;width: 200px;"></div>
    
    <script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/gh/tronic247/material/dist/js/material.min.js"></script>
    </body>
    </html>

    【讨论】:

      猜你喜欢
      • 2014-08-21
      • 1970-01-01
      • 2017-07-20
      • 2017-07-01
      • 2015-06-09
      • 1970-01-01
      • 2022-01-20
      • 2020-09-18
      • 2020-12-10
      相关资源
      最近更新 更多