I want to replicate the gif animation below with pure CSS, is it possible, and how? I'm also open to other appropriate approaches.
The snippet below is what I have so far it's only a static face.
body {
background: #fff;
margin: 50px;
}
.loader {
position: relative;
border-radius: 50%;
box-sizing: border-box;
width: 80px;
height: 80px;
background: linear-gradient(to bottom, #fff 50%, #51cf66 50%);
}
.loader:before {
content: "";
position: absolute;
left: 10px;
right: 10px;
top: 10px;
bottom: 10px;
border-radius: 50%;
background: #fff;
}
.dot {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: #51cf66;
}
.dot:first-child {
left: 10px;
top: 10px;
}
.dot:last-child {
right: 10px;
top: 10px;
}
<div class="loader">
<div class="dot"></div>
<div class="dot"></div>
</div>
hmmm for that sort of animation, adjusting a SVG from CSS would be much preferred.
The technique you're using to draw a full CSS smiley relies on a liner-background to adjust the "length" of the line, which would be ridiculously resource intensive trying to animate (to increase the length) while rotating.
Closest I could get tweaking your code with pure CSS and no SVG is this, hope it helps
body {
background: #fff;
margin: 50px;
}
.smiley{
position:relative;
width: 70px;
height: 70px;
}
.loader {
position: relative;
border-radius: 50%;
box-sizing: border-box;
width: 70px;
height: 70px;
background: linear-gradient(to bottom, #fff 50%, #51cf66 50%);
animation: rotate 2s infinite;
}
.loader:before {
content: "";
position: absolute;
left: 8px;
right: 8px;
top: 8px;
bottom: 8px;
border-radius: 50%;
background: #fff;
}
.dot-left, .dot-right {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: #51cf66;
}
.dot-left {
left: 4px;
top: 10px;
animation: dissapearL 2s infinite;
}
.dot-right {
right: 6px;
top: 10px;
animation: dissapearR 2s infinite;
}
@keyframes rotate{
from{
transform:rotate(0);
}
to{
transform:rotate(720deg);
}
}
@keyframes dissapearL{
0%{
transform:scale(1);
}
10%{
transform:scale(1);
}
15%{
transform:scale(0);
}
45%{
transform:scale(0);
}
50%{
transform:scale(1);
}
}
@keyframes dissapearR{
0%{
transform:scale(1);
}
15%{
transform:scale(1);
}
20%{
transform:scale(0);
}
65%{
transform:scale(0);
}
70%{
transform:scale(1);
}
}
<div class="smiley ">
<div class="loader">
</div>
<div class="dot-left"></div>
<div class="dot-right"></div>
</div>
Update 2020
A new optimized version with less of code where I will make the edge of smile more realistic:
.smile {
margin: 30px;
width: 100px;
height: 100px;
border-radius: 50%;
background:
radial-gradient(circle closest-side,#42bd56 99%,transparent)
-35px 42px/calc(100% - 15px) 15px repeat-x,
linear-gradient(#fff,#fff) top/100% 50px no-repeat,
radial-gradient(farthest-side,transparent calc(99% - 15px),#42bd56 calc(100% - 15px));
position: relative;
}
.smile:before,
.smile:after{
content: "";
position: absolute;
width:100%;
top: calc(50% - 7px);
height: 15px;
background:radial-gradient(circle closest-side,#42bd56 99%,transparent) -7.5px 0/200% 100%;
transform:rotate(calc(var(--o,0deg) - 50deg));
}
.smile:after {
--o:-80deg;
}
.smile:hover {
animation: rotateS 1.2s linear;
}
.smile:hover:before,
.smile:hover:after{
animation: inherit;
animation-name:rotateE;
}
@keyframes rotateS {
50% {
transform: rotate(360deg);
background-size: calc(90% - 15px) 15px,100% 30px,auto;
background-position:-25px 22px,top,0 0;
}
100% {
transform: rotate(720deg);
}
}
@keyframes rotateE {
30%,70% {
transform:rotate(calc(var(--o,0deg) - 180deg));
}
}
Hover to animate
<div class="smile">
</div>
A Codepen demo to play with the code
Old answer
Here is the solution and below the explanation step by step
.smile {
margin: 30px;
width: 100px;
height: 100px;
border-radius: 50%;
background:
radial-gradient(circle ,#42bd56 10%,transparent 11%) 43px 0px,
radial-gradient(circle ,#42bd56 10%,transparent 11%) -43px 0px,
radial-gradient(circle , #fff 35px, transparent 35px),
linear-gradient(to bottom, #fff 50%, transparent 50%),
#42bd56;
background-repeat:no-repeat;
position: relative;
}
.smile:before,.smile:after{
content: "";
position: absolute;
width: 100%;
top: 50%;
margin-top: -7px;
height: 15px;
background:
radial-gradient(circle,#42bd56 15%,transparent 16%) 43px 0px;
transform:rotate(-50deg);
}
.smile:after {
background:
radial-gradient(circle at center,#42bd56 15%,transparent 16%) -43px 0px;
transform:rotate(50deg);
}
.smile:hover {
animation: rotateS 1.5s linear;
}
.smile:hover::before {
animation: rotateL 1.5s linear;
}
.smile:hover::after {
animation: rotateR 1.5s linear;
}
@keyframes rotateS {
0% {
transform: rotate(0deg);
background-size: auto,auto,auto ,100% 100%, auto;
}
50% {
transform: rotate(360deg);
background-size: auto,auto,auto, 100% 50%, auto;
}
100% {
transform: rotate(720deg);
background-size: auto,auto,auto, 100% 100%, auto;
}
}
@keyframes rotateR {
0% {
transform:rotate(50deg);
}
30%,70% {
transform:rotate(-40deg);
}
100% {
transform:rotate(50deg);
}
}
@keyframes rotateL {
0% {
transform:rotate(-50deg);
}
30%,70% {
transform:rotate(-180deg);
}
100% {
transform:rotate(-50deg);
}
}
Hover to animate
<div class="smile">
</div>
In this solution, I will be using only one element. I am going to rely on multiple backgrounds with linear-gradient and radial-gradient and also use pseudo-element.
I will use a linear-gradient and a radial gradient with a background-color to create the main shape, then I will add 2 small radial gradient for the rounded corner:
.smile {
margin: 50px;
width: 100px;
height: 100px;
border-radius: 50%;
background:
radial-gradient(circle,#42bd56 10%,transparent 11%) 43px 0px,
radial-gradient(circle,#42bd56 10%,transparent 11%) -43px 0px,
radial-gradient(circle, #fff 35px, transparent 35px),
linear-gradient(to bottom, #fff 50%, transparent 50%),
#42bd56;
background-repeat:no-repeat;
position: relative;
}
<div class="smile">
</div>
Now to rotate the smile I will use rotation and also change the size of the linear gradient. As you can see in the GIF, during rotation the curve increase so by changing the size of the linear-gradient I will increase it.
As you can notice the small radial gradient used for the rounded corner are hidden after the increase, so we can also consider moving them at the same time but I will not do it so the code don't get complicated.
Here is the animation (hover to activate):
.smile {
margin: 50px;
width: 100px;
height: 100px;
border-radius: 50%;
background:
radial-gradient(circle at center,#42bd56 10%,transparent 11%) 43px 0px,
radial-gradient(circle at center,#42bd56 10%,transparent 11%) -43px 0px,
radial-gradient(circle at center, #fff 35px, transparent 35px),
linear-gradient(to bottom, #fff 50%, transparent 50%),
#42bd56;
background-repeat:no-repeat;
position: relative;
}
.smile:hover {
animation: rotateS 1.5s linear;
}
@keyframes rotateS {
0% {
transform: rotate(0deg);
background-size: auto,auto,auto ,100% 100%, auto;
}
50% {
transform: rotate(360deg);
background-size: auto,auto,auto, 100% 50%, auto;
}
100% {
transform: rotate(720deg);
background-size: auto,auto,auto, 100% 100%, auto;
}
}
<div class="smile">
</div>
For the eyes, I will use pseudo element and radial gradient to create the circle. I will not simply create a circle with border-radius as I need also to rotate them on the same axis as the smile.
So the idea is to make them full width and use the radial gradient to have the circle at the most right/left. By doing this I can easily control their position with rotation and be sure they stay at the need postition whataver the rotation degree.
.smile {
margin: 50px;
width: 100px;
height: 100px;
border-radius: 50%;
background:
radial-gradient(circle at center,#42bd56 10%,transparent 11%) 43px 0px,
radial-gradient(circle at center,#42bd56 10%,transparent 11%) -43px 0px,
radial-gradient(circle at center, #fff 35px, transparent 35px) ,
linear-gradient(to bottom, #fff 50%, transparent 50%),
#42bd56;
position: relative;
}
.smile:before,.smile:after{
content: "";
position: absolute;
width: 100%;
top: 50%;
margin-top: -7px;
height: 15px;
background: radial-gradient(circle,#42bd56 15%,transparent 16%) 43px 0px;
transform:rotate(-50deg);
}
.smile:after {
background:radial-gradient(circle,#42bd56 15%,transparent 16%) -43px 0px;
transform:rotate(50deg);
}
<div class="smile">
</div>
Now for animation, I will keep the first rotation that will also rotate the eyes but I will add some animation to the eyes to make them move in the opposite direction and create the illusion of the smile going above them. So I will simply add a negative rotation to the eyes so they seem to move slower and thus go above the smile and create the needed effect:
Here is again the full animation :)
.smile {
margin: 50px;
width: 100px;
height: 100px;
border-radius: 50%;
background:
radial-gradient(circle at center,#42bd56 10%,transparent 11%) 43px 0px,
radial-gradient(circle at center,#42bd56 10%,transparent 11%) -43px 0px,
radial-gradient(circle at center, #fff 35px, transparent 35px) ,
linear-gradient(to bottom, #fff 50%, transparent 50%),
#42bd56;
background-repeat:no-repeat;
position: relative;
}
.smile:before,.smile:after{
content: "";
position: absolute;
width: 100%;
top: 50%;
margin-top: -7px;
height: 15px;
background: radial-gradient(circle,#42bd56 15%,transparent 16%) 43px 0px;
transform:rotate(-50deg);
}
.smile:after {
background:radial-gradient(circle,#42bd56 15%,transparent 16%) -43px 0px;
transform:rotate(50deg);
}
.smile:hover {
animation: rotateS 1.5s linear;
}
.smile:hover::before {
animation: rotateL 1.5s linear;
}
.smile:hover::after {
animation: rotateR 1.5s linear;
}
@keyframes rotateS {
0% {
transform: rotate(0deg);
background-size: auto,auto,auto ,100% 100%, auto;
}
50% {
transform: rotate(360deg);
background-size: auto,auto,auto, 100% 50%, auto;
}
100% {
transform: rotate(720deg);
background-size: auto,auto,auto, 100% 100%, auto;
}
}
@keyframes rotateR {
0% {
transform:rotate(50deg);
}
30%,70% {
transform:rotate(-40deg);
}
100% {
transform:rotate(50deg);
}
}
@keyframes rotateL {
0% {
transform:rotate(-50deg);
}
30%,70% {
transform:rotate(-180deg);
}
100% {
transform:rotate(-50deg);
}
}
<div class="smile">
</div>
In this solution, I used a linear transition for simplicity but it can be changed to any ease function to have a similar transition as the GIF
Although this smiley animation can be achieved using only CSS, as others have said, SVG is clearly a better option mainly for these reasons :
This said, I made an example of your smiley animation.
This is what is looks like :
And here is the code (simplified from the codepen demo) :
svg {
width:100px;
height:auto;
display:block;
transform:rotateZ(0deg);
margin:0 auto;
}
.smile, .eyes {
stroke:teal;
stroke-width:1.3;
stroke-linecap:round;
fill:transparent;
}
svg:hover {animation:rotate 1.2s cubic-bezier(0.65, 0.000, 0.75, 1.000);}
svg:hover .smile{animation: smile 1s cubic-bezier(0.2, 0.000, 0.8, 1.000);}
svg:hover .eyes{animation: eyes 1s cubic-bezier(.7, 0.000, 0.4, 1.000);}
@keyframes rotate { to { transform:rotateZ(720deg); } }
@keyframes smile { 50% { stroke-dasharray:20,5.1327;} }
@keyframes eyes { 70% { stroke-dasharray:1,0,.5,23.6327;} }
h1 {text-align:center;color:teal;}
<svg viewbox="0 0 10 10">
<circle class="smile" cx="5" cy="5" r="4" stroke-dashoffset="-.5" stroke-dasharray="11.5,13.6327" />
<circle class="eyes" cx="5" cy="5" r="4" stroke-dashoffset="-15.5" stroke-dasharray="0,6.6327,0,17.5" />
</svg>
<h1>Hover me !</h1>
This smiley is made with two SVG circle elements (one for the eyes and one for the smile) and the stroke-dasharray attribute to make the eyes and the smile.
Animation :
The svg is rotated on hover using CSS animation and the stroke-dasharray attribute is animated to make the eyes "disapear" in the smile. The lenght of the smile is also changed to about 3/4 of a circle.
Easing function :
The smooth effect is made with the bezier curve easing functions. The animation timing are also tweaked to get closer to the desired smiley animation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With