0

I am trying to make a fancy animation only in CSS. I started with a tutorial on W3 School and wanted to make it better. My idea is to have a square loader turning clockwise while another inside would turn in the opposite direction.

On this link you will see what I'm talking about, the only difference is that I would like the red part to be turning in the opposite direction.

In order to do so I tried adding another div with class name .spinner. Here's my try at it: https://jsfiddle.net/avhjj4ps/

.loader-container {
  position: absolute;
  left: calc(50% - 75px);
  width: 150px;
  height: 150px;
  padding: 5px;
  border: 1px solid red;
  top: calc(50% - 75px);
}
img {
  width: 200px;
  margin: 20px;
  /*animation: move 2s alternate infinite linear;*/
}

#myClip, #svg {
  position: absolute;
  left: 50%;
  top: 50%;
}


.loader, .spinner {
position: absolute;

}
.loader {
  left: calc(50% - 35px);
  top: calc(50% - 35px);
  width: 40px;
  height: 40px;
  border: 15px solid transparent;
  border-top: 15px solid none;
  /*-webkit-animation: loader 2s linear infinite;
  animation: loader 2s linear infinite;*/
}
.spinner {
  left: calc(50% - 55.1px);
  top: calc(50% - 55.1px);
  /*clip-path: url(#myClip);*/
  width: 40px;
  border-radius: 50%;
  height: 40px;
  border: 36px solid #f3f3f3;
  border-top: 36px solid #5cb85c;
  /*-webkit-animation: spin 2s linear infinite;
  animation: spin 2s linear infinite;*/
}

@-webkit-keyframes loader {
  0% { -webkit-transform: rotate(0deg); }
  100% { -webkit-transform: rotate(360deg); }
}

@keyframes loader {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

@-webkit-keyframes spin {
  0% { -webkit-transform: rotate(0deg); }
  100% { -webkit-transform: rotate(-360deg); }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(-360deg); }
}
<div class="loader-container">
 <div class="loader"></div>
<div class="spinner"></div>
  
<svg id="svg" width="0" height="0">
  <defs>
    <clipPath id="myClip">
      <rect x="-35" y="-35" width="15" height="70" />
      <rect x="20" y="-35" width="15" height="70" />
      <rect x="-35" y="-35" width="70" height="15" />
      <rect x="-35" y="20" width="70" height="15" />
      
    </clipPath>
  </defs>
</svg>
  
</div>

I am trying to show the green spinner only where there is the square loader. It would be like a mask. In the above snippet (also available here: http://codepen.io/anon/pen/ZOoByA), I'm trying to use the clip-path property. Can some tell me why clip-path: url(#myClip); doesn't work ? When I comment this line the loader shows completely, however while active it's not showing at all.

Community
  • 1
  • 1
Ivan
  • 34,531
  • 8
  • 55
  • 100

3 Answers3

2

For a CSS-only solution without SVG you need some helper elements:

<div class="loader">
    <div class="square"></div>
    <div class="cutter">
        <div class="spinner">
        </div>
    </div>
</div>

And then this CSS code:

.square {
  width: 40px;
  height: 40px;
  background: #f3f3f3;
  z-index: 1;
}

.cutter {
  width: 70px;
  height: 70px;
  left: -15px;
  top: -15px;
  overflow: hidden;
}

.spinner {
  width: 54px;
  border-radius: 50%;
  height: 54px;
  border: 8px solid transparent;
  border-top: 8px solid #5cb85c;
  -webkit-animation: spin 1s linear infinite;
  animation: spin 1s linear infinite;
  margin-left: -15px;
  margin-top: -15px;
}

Result: https://jsfiddle.net/avhjj4ps/3/

Disadvantage: Inner square must have a solid background (no gradient or image) if it has to match the parent's / body's background.

Felix Edelmann
  • 4,959
  • 3
  • 28
  • 34
  • thanks for the answer but when I said inside I meant actually inside the grey part of the square loader... Do you have any ideas how to do that ? – Ivan Jul 23 '16 at 23:12
  • Sorry, I was thinking more like the green part would flow inside the square – Ivan Jul 23 '16 at 23:19
  • Updated again; now it works (I hope) as expected, but you need some more elements. – Felix Edelmann Jul 23 '16 at 23:50
  • Great thank you! Yes it works, I didn't think of using overflow: hidden. I was losing hope trying everything I could with clip-paths... What are the "other elements" ? – Ivan Jul 23 '16 at 23:55
  • .cutter cuts the green spinner outside, .square is a white square overlay. – Felix Edelmann Jul 23 '16 at 23:57
  • Oh I realise it now but I have a small issue. The square inside is white, is there a way to se through it while still blocking the display of the green part ? Isn't there a way to mask it with svg ? – Ivan Jul 24 '16 at 13:16
  • 1
    @Ivan, as this is a completely different solution, I posted a new answer. – Felix Edelmann Jul 26 '16 at 15:30
2

You can create your loader in svg with some polygons and then clip the inner green loader away with clipPath.

First, define the gray border as a polygon:

<polygon id="loader" points="0,0 0,70 70,70 70,0 0,0 15,15 55,15 55,55 15,55 15,15" />

As we will reuse this shape (the actual loader and the clip-path shape), we put into the defs tag:

<svg height="0" width="0">
    <defs>
        <polygon id="loader" points="..." />
    </defs>
</svg>

Then we put the clipPath into the same defs tag:

<clipPath id="loaderClipper">
    <use xlink:href="#loader" x="15" y="15" />
</clipPath>

The offset of 15 is calculated in the following way: The loader's width is 70, but if it is rotated by 45 degrees, it's width is 70√2 which rounds to 100. The whitespace in the left and in the right is (100 - 70) / 2 = 15.

The svg for the actual used element looks like this:

<svg width="100" height="100" viewbox="0 0 100 100" clip-path="url(#loaderClipper)">
    <use xlink:href="#loader" class="loader" x="15" y="15" />
    <polygon class="spinner" points="0,0 100,0 50,50" x="30" y="30" />
</svg>

And some css for colors and the animation:

svg {
  animation: rotate 2s linear infinite;
  transform-origin: 50px 50px;
}

.loader {
  fill: #dcdada;
}

.spinner {
  fill: #5cb85c;
  animation: rotate 1s linear infinite reverse;
  transform-origin: 50px 50px;
}

Result fiddle: https://jsfiddle.net/apLepsv3/10/

Successfully tested on both mobile and desktop Firefox and Chrome.

Felix Edelmann
  • 4,959
  • 3
  • 28
  • 34
1

You can create the spinner with HTML and CSS and then cut the overflow away by using the clip-path property in combination with a svg <clipPath> element.

Your html structure of the spinner:

<div class="loader">
    <div class="spinner">
    </div>
</div>

Now position the two elements over each other:

.loader {
  width: 40px;
  height: 40px;
  position: relative;
  left: 30px;
  top: 30px;
  border: 15px solid #dcdada;
  border-top: 15px solid none;
  -webkit-animation: loader 2s linear infinite;
  animation: loader 2s linear infinite;
}

.spinner {
  width: 0px;
  height: 0px;
  position: relative;
  left: -30px;
  top: -30px;
  border: 50px solid transparent;
  border-top: 50px solid #5cb85c;
  -webkit-animation: spin 1s linear infinite;
  animation: spin 1s linear infinite;
}

But there's still that green overflow outside and inside of the gray border. So we need to cut it away with a svg <polygon>.

<svg height="0" width="0">
    <defs>
        <clipPath id="loaderClipper">
            <polygon points="0,0 0,70 70,70 70,0 0,0 15,15 55,15 55,55 15,55 15,15"/>
        </clipPath>
    </defs>
</svg>

The points define a 70x70 square with a 40x40 square cut off.

Then add the clip-path property that references to the svg <clipPath> element:

.loader {
  clip-path: url(#loaderClipper);
}

Fiddle: https://jsfiddle.net/apLepsv3/2/

Disadvantage: Only supported in Firefox, not Chrome

Felix Edelmann
  • 4,959
  • 3
  • 28
  • 34
  • Hey there, I tried the same technique, did it work on your computer. On mine I don't have the green overflow deleted by the svg polygon : https://s31.postimg.org/xerqu3j5n/image.png – Ivan Jul 26 '16 at 17:10
  • Aaah, browser support. clip-path on html elements works only in Firefox. See this post: http://stackoverflow.com/questions/19227849/clip-path-does-not-work-with-chrome What about using a round loader? https://jsfiddle.net/vw938pgt/ – Felix Edelmann Jul 26 '16 at 17:57
  • 1
    Oh! I think that's why my tests didn't work, I was on Chrome and never tried to test it on Firefox... Thanks! You helped me a lot @Felix. – Ivan Jul 26 '16 at 20:51
  • It has been a while... I have a question, why is [this](http://stackoverflow.com/questions/19227849/clip-path-does-not-work-with-chrome) working on chrome and not my preloader? – Ivan Aug 20 '16 at 17:55
  • Apparently, clip-path works on chrome when using it on images (but not divs), probably because they're easily converted to svg images. You can avoid the browser support by using svg paths instead of html divs (see my next answer). – Felix Edelmann Aug 22 '16 at 09:58