4

I have problem with finding the right position of click. I want to make google material design - ripple effect on clicked button. Circle need to be on button not somewhere else. So when you click on button white circle is showing somewhere else not above the wanted button. Where is the mistake i made?

$(function () {
    var btnClick, bWidth, bHeight, x, y, posX, posY,d;
    $(".btn").click(function (e) {
        e.preventDefault();

        posX = $(this).offset().left;
        posY = $(this).offset().top;
        bWidth = $(this).outerWidth();
        bHeight = $(this).outerHeight();
        d = Math.max(bWidth,  bHeight);
        
        $(".btn-over").remove();

        if ($(this).find(".btn-over").length === 0) {
            $(this).prepend("<span class='btn-over'></span>");
        }

//        btnClick = $(this).children(".btn-over");
//        btnClick.removeClass("animation");
//            if (!btnClick.height() && !btnClick.width()) {
//            d = Math.max($(this).outerWidth(), $(this).outerHeight());
//            btnClick.css({
//                height: d,
//                width: d
//            });
//        }

        x = e.pageX - posX - bWidth / 2;
        y = e.pageY - posY - bHeight /2;

        $(".btn-over").css({
            width: d,
            height: d,
            top: y + 'px',
            left: x + 'px'
        }).addClass("animation");
    });
});
nav {
  height: 3rem;
  background-color: #424242;
  color: #fff; }

.menu {
  list-style: none;
  float: right; }
  .menu li {
    display: inline-block; }

.btn-sigup {
  box-shadow: none;
  background-color: #4CAF50; }
  .btn-sigup:hover {
    background-color: #66BB6A;
    box-shadow: none; }

.btn-login {
  box-shadow: none;
  background-color: transparent; }
  .btn-login:hover {
    box-shadow: none;
    background-color: transparent; }

.btn-over {
  display: inline-block;
  position: absolute;
  background: rgba(255, 255, 255, 0.3);
  border-radius: 100%;
  -webkit-transform: scale(0);
  -moz-transform: scale(0);
  -o-transform: scale(0);
  transform: scale(0); }

.animation {
  -webkit-animation: ripple 0.65s linear;
  -moz-animation: ripple 0.65s linear;
  -ms-animation: ripple 0.65s linear;
  -o-animation: ripple 0.65s linear;
  animation: ripple 0.65s linear; }

@-webkit-keyframes ripple {
  100% {
    opacity: 0;
    -webkit-transform: scale(2.5); } }

@-moz-keyframes ripple {
  100% {
    opacity: 0;
    -moz-transform: scale(2.5); } }

@-o-keyframes ripple {
  100% {
    opacity: 0;
    -o-transform: scale(2.5); } }

@keyframes ripple {
  100% {
    opacity: 0;
    transform: scale(2.5); } }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<header>
        <nav>
            <ul class="menu">
                <li>
                    <button class="btn btn-login">LOG IN</button>
                </li>
                <li>
                    <button class="btn btn-sigup">SING UP</button>
                </li>
            </ul>
        </nav>
    </header>
hongchen
  • 85
  • 1
  • 11

1 Answers1

7

There's nothing wrong with your JavaScript, you just need to set the position of the buttons to relative so that the positioning of the .btn-over span is contained within them. You should also consider setting the overflow of the buttons to hidden so that the "ripple" doesn't spill out of them.

$(function(){
  var btnClick,bWidth,bHeight,x,y,posX,posY,d;
  $(".btn").click(function(e){
    e.preventDefault();
    posX=$(this).offset().left;
    posY=$(this).offset().top;
    bWidth=$(this).outerWidth();
    bHeight=$(this).outerHeight();
    d=Math.max(bWidth,bHeight);
    $(".btn-over").remove();
    if($(this).find(".btn-over").length===0)
      $(this).prepend("<span class=\"btn-over\"></span>");
    x=e.pageX-posX-bWidth/2;
    y=e.pageY-posY-bHeight/2;
    $(".btn-over").css({
      width:d+"px",
      height:d+"px",
      top:y+"px",
      left:x+"px"
    }).addClass("animation");
  });
});
nav{
  background-color:#424242;
  color:#fff;
  height:3rem;
}
.menu{
  float:right;
  list-style:none;
}
.menu li{
  display:inline-block;
}
.btn-sigup{
  background-color:#4CAF50;
  overflow:hidden;
  position:relative;
}
.btn-sigup:hover{
  background-color:#66BB6A;
}
.btn-login{
  background-color:transparent;
  overflow:hidden;
  position:relative;
}
.btn-over{
  background:rgba(255, 255, 255, 0.3);
  border-radius:50%;
  display:inline-block;
  position:absolute;
  transform:scale(0);
}
.animation{
  animation:ripple .65s linear;
}
@keyframes ripple{
  to{
    opacity:0;
    transform:scale(2.5);
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<header>
  <nav>
    <ul class="menu">
      <li>
        <button class="btn btn-login">LOG IN</button>
      </li>
      <li>
        <button class="btn btn-sigup">SING UP</button>
      </li>
    </ul>
  </nav>
</header>

And here' an alternative implementation, with some extra features, that you can use on any element you wish. To implement it, you'll need to copy the JavaSript and the important stuff from the CSS and then you just give any element you want to apply this effect to a data-ripple-color attribute, which takes a value of any valid CSS color.

To apply this effect to an element every time it is clicked without waiting for the previous animation to complete add a data-ripple-multiple attribute to it with a value of true. See the button element below for an example.

(function(){
  if(document.querySelector("[data-ripple-color]")){
    var span=document.createElement("span");
    span.classList.add("ripple");
    document.addEventListener("click",function(event){
      var target=event.target,color,data,multi,node,style;
      while(!target.dataset.rippleColor&&target!==document.body)
        target=target.parentNode;
      data=target.dataset;
      multi=data.rippleMultiple;
      if((color=data.rippleColor)&&(multi||!data.rippleWait)){
        if(!multi)data.rippleWait="true";
        target.appendChild(node=span.cloneNode(0));
        style=node.style;
        style.background=color;
        style.height=style.width=Math.min(target.offsetHeight,target.offsetWidth)+"px";
        style.left=event.pageX-target.offsetLeft+"px";
        style.top=event.pageY-target.offsetTop+"px";
        setTimeout(function(){
          target.removeChild(node);
          if(!multi)delete data.rippleWait;
        },750);
      }
    },0);
  }
})();
/* Housekeeping */@import url(https://fonts.googleapis.com/css?family=Roboto:400,300,500,700,900);*,*::before,*::after{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;background-clip:padding-box;border:0;border-radius:0;box-sizing:border-box;color:rgba(0,0,0,.87);font-family:Roboto,arial,sans-serif;font-size:14px;-webkit-font-smoothing:antialiased;font-style:normal;font-weight:500;line-height:1.2em;list-style:none;margin:0;outline:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0);text-align:left;text-decoration:none;text-indent:0;text-rendering:auto;transition-duration:.2s;transition-property:none;transition-timing-function:cubic-bezier(.4,0,.2,1);}*>*{font-size:inherit;font-style:inherit;font-weight:inherit;}*>*,*::before,*::after{color:inherit;font-family:inherit;line-height:inherit;}
/* The important stuff */
[data-ripple-color]{
  overflow:hidden;
  position:relative;
}
.ripple{
  animation:ripple 1s cubic-bezier(.4,0,.2,1);
  border-radius:50%;
  display:block;
  opacity:0;
  position:absolute;
}
@keyframes ripple{
  from{
    transform:translate(-50%,-50%) scale(1);
    opacity:.54;
  }to{
    transform:translate(-50%,-50%) scale(54);
    opacity:0;
  }
}
/* Fiddle styles */
a[data-ripple-color],button[data-ripple-color]{
  border-radius:3px;
  cursor:pointer;
  display:block;
  font-size:24px;
  font-weight:500;
  line-height:40px;
  margin:0 auto 8px;
  padding:8px;
  text-align:center;
  width:200px;
}
a[data-ripple-color]{
  background:#F44336;
  color:#fff;
}
button[data-ripple-color]{
  background:#3f51b5;
  color:#fff;
  font-size:24px;
  line-height:40px;
  padding:8px;
  text-align:center;
  text-transform:uppercase;
  width:200px;
}
figure[data-ripple-color]{
  border-radius:3px;
  margin:0 auto 8px;
  width:200px;
}
p[data-ripple-color]{
  line-height:20px;
  margin:0 8px 8px;
  padding:8px;
}
<button data-ripple-color="#fff" data-ripple-multiple="true">Button</button>
<a data-ripple-color="#303f9f">Link</a>
<figure data-ripple-color="rgb(0,0,0)"><img src="http://placehold.it/200x200.png/e0e0e0?text=Image+%0A+Parent"></figure>
<p data-ripple-color="#616161">Paragrpah</p>
Shaggy
  • 6,368
  • 2
  • 22
  • 42
  • OMG....i really forgot that css maybe can be a problem I was focused on js file. Thank you. – hongchen Apr 27 '16 at 11:34
  • 1
    Easily done; fresh eyes can work wonder sometimes :) I've updated my answer with an alternative implementation, which might be of use to you. – Shaggy Apr 27 '16 at 13:12
  • Thank you again. Second solution is more complex and also is not used in material design. But is good solution for everything you want to have ripple effect. I'll think about implementing in my project. I think this will be good solution for everyone who is looking for google material design ripple effect. P.S - Is more easy to build this with jQuery? – hongchen Apr 27 '16 at 21:34
  • It's lways better to use plain ol' normal JavaScript. When you say "*[it's] not used in Material Design*", what do you mean? If the ripples don't suit your needs you can adjust them by editing their CSS. – Shaggy Apr 27 '16 at 22:33
  • 1
    If you mean Material Design doesn't apply a ripple every time you click an element - rather, it waits until the previous ripple has completed - I have updated the alternative implementation to reflect that. And, if you need/like, you can still have it apply the ripple effect to an element *every* time its clicked using the new `data-` attribute. – Shaggy Apr 29 '16 at 13:31
  • The last one is great because it can be used in many different elements. Thanks a lot. – hongchen May 11 '16 at 12:31