Creating a pendulum effect using CSS3 animations

I am trying to achieve a pendulum effect using pure CSS, but I'm facing some issues with the smoothness of the animation.

This is what I am aiming for, but created purely with CSS:

However, I would like the animation to resemble more natural speed variations based on its position.

Fiddle

.bellImg {
  height: 20px;
  width: 20px;
  position: absolute;
  right: 10px;
  top: 18px;
  -webkit-animation-name: rotate;
  animation-delay: 3s;
  -webkit-animation-duration: 2s;
  -webkit-animation-iteration-count: infinite;
  -webkit-animation-direction: linear;
  -webkit-transform-origin: 50% 0%;
  -webkit-animation-timing-function: ease-in-out;
}
@-webkit-keyframes rotate {
  0% {
    -webkit-transform: rotate(0deg);
  }
  10% {
    -webkit-transform: rotate(10deg);
  }
  20% {
    -webkit-transform: rotate(20deg);
  }
  30% {
    -webkit-transform: rotate(10deg);
  }
  40% {
    -webkit-transform: rotate(5deg);
  }
  50% {
    -webkit-transform: rotate(0deg);
  }
  60% {
    -webkit-transform: rotate(-5deg);
  }
  70% {
    -webkit-transform: rotate(-10deg);
  }
  80% {
    -webkit-transform: rotate(-20deg);
  }
  90% {
    -webkit-transform: rotate(-10deg);
  }
  100% {
    -webkit-transform: rotate(0deg);
  }
}
<img class="bellImg" src="img/bell.png">

Answer №1

There are a number of issues in the code provided:

  • The animation-timing-function is currently set to ease-in-out, which means the animation starts and ends slowly with increased speed in between. To achieve a smoother and consistent motion, it should be changed to linear.

    According to MDN's explanation of ease-in-out timing function:

    This keyword represents the timing function cubic-bezier(0.42, 0.0, 0.58, 1.0). With this timing function, the animation starts slowly, accelerates then slows down when approaching its final state. At the beginning, it behaves similarly to the ease-in function; at the end, it is similar to the ease-out function.

  • There is no valid value called linear for animation-direction.
  • The degree of rotation varies across the splits - some sections rotate by 10 degrees for a 10% gap while others only rotate by 5 degrees. It is recommended to make these rotations even.

By implementing all the necessary corrections as shown below, a smooth animation can be achieved.

.bellImg {
  height: 20px;
  width: 20px;
  position: absolute;
  right: 10px;
  top: 18px;
  -webkit-animation-name: rotate;
  animation-delay: 3s;
  -webkit-animation-duration: 2s;
  -webkit-animation-iteration-count: infinite;
  -webkit-animation-direction: normal;
  -webkit-transform-origin: 50% 0%;
  -webkit-animation-timing-function: linear;  /* or implement a custom easing */
}
@-webkit-keyframes rotate {
  0% {
    -webkit-transform: rotate(0deg);
  }
  25% {
    -webkit-transform: rotate(20deg);
  }
  75% {
    -webkit-transform: rotate(-20deg);
  }
  100% {
    -webkit-transform: rotate(0deg);
  }
}
<img class="bellImg" src="https://cdn1.iconfinder.com/data/icons/freeline/32/bell_sound_notification_remind_reminder_ring_ringing_schedule-48.png">


Adjusting the animation speed relative to position (slowing down at extremes and speeding up in the middle) using pure CSS alone is not feasible (even with additional elements).

To control the animation speed based on position, consider following these steps:

  • Place the image inside a container element and animate it to rotate from 20 degrees to -40 degrees.
  • Initiate the parent's animation slightly earlier than the child's by 1/3rd of the total duration of both animations. This results in reducing the delay on the parent by 0.66s. This adjustment ensures the parent offsets the initial rotation of the child. The 1/3rd difference accounts for the time taken by the parent to reach 0 degrees.
  • Modify the keyframes for the image's animation so that it rotates from -20 degrees to 40 degrees.
  • Set the animation-direction property to alternate for both animations, making them progress forward in one iteration and reverse in the next, creating a continuous cycle.
  • Utilize the animation-timing-function of ease-in-out to slow down as it nears the extremes. This effect becomes more noticeable with longer animation durations.

.container {
  position: absolute;
  height: 20px;
  width: 20px;
  /* right: 10px; commented for demo */
  top: 18px;
  transform: rotate(20deg);
  animation-name: rotate-container;
  animation-delay: 2.33s;
  animation-duration: 2s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  transform-origin: 50% 0%;
  animation-timing-function: ease-in-out;
}
.bellImg {
  height: 100%;
  width: 100%;  
  transform: rotate(-20deg);
  animation-name: rotate;
  animation-delay: 3s;
  animation-duration: 2s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  transform-origin: 50% 0%;
  animation-timing-function: ease-in-out;
}
@keyframes rotate {
  0% {
    transform: rotate(-20deg);
  }
  100% {
    transform: rotate(40deg);
  }
}
@keyframes rotate-container {
  0% {
    transform: rotate(20deg);
  }
  100% {
    transform: rotate(-40deg);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class='container'>
  <img class="bellImg" src="https://cdn1.iconfinder.com/data/icons/freeline/32/bell_sound_notification_remind_reminder_ring_ringing_schedule-48.png">
</div>

The Prefix-free library was included in the snippet solely to eliminate browser prefixes.

Answer №2

One way to depict the motion of a pendulum is through sinusoidal movement.

An animated representation of this movement can be created using the following code:

.base {
  height: 600px;
  width: 10px;
  position: absolute;
  animation: base 10s infinite linear;
  background-color: lightgray;
  transform: translateX(588px);
}

@keyframes base {
  from {transform: translateX(77px);}
  to {transform: translateX(760px);}
}

.element {
  height: 10px;
  width: 10px;
  background-color: green;
  border-radius: 100%;
  animation: element 10s infinite;
  transform: translateY(553px);
}

@keyframes element {
  from {transform: translateY(294px); animation-timing-function: ease-out;}
  25% {transform: translateY(36px); animation-timing-function: ease-in;}
  50% {transform: translateY(294px); animation-timing-function: ease-out;}
  75% {transform: translateY(553px); animation-timing-function: ease-in;}
  to {transform: translateY(294px);}
}

.ref {
  width: 800px;
  height: 600px;
  background-image: url(https://i.sstatic.net/Fx4bR.png);
  background-size: cover;
}
<div class="base">
<div class="element">
</div>
</div>
<div class="ref"></div>

Certain manual adjustments were applied to accommodate the background image, emphasizing the importance of timing functions for accuracy.

If the standard functions do not meet your requirements, custom cubic bezier functions can be implemented for precise control.

.base {
  height: 600px;
  width: 10px;
  position: absolute;
  animation: base 10s infinite linear;
  background-color: lightgray;
  transform: translateX(588px);
}

@keyframes base {
  from {transform: translateX(77px);}
  to {transform: translateX(760px);}
}

.element {
  height: 10px;
  width: 10px;
  background-color: green;
  border-radius: 100%;
  animation: element 10s infinite;
  transform: translateY(553px);
}

@keyframes element {
  from {transform: translateY(294px); 
        animation-timing-function: cubic-bezier(0.1, 0.3, 0.3, 1);}
  25% {transform: translateY(36px); 
        animation-timing-function: cubic-bezier(0.7, 0.0, 0.9, 0.7);}
  50% {transform: translateY(294px); 
        animation-timing-function: cubic-bezier(0.1, 0.3, 0.3, 1);}
  75% {transform: translateY(553px); 
        animation-timing-function: cubic-bezier(0.7, 0.0, 0.9, 0.7);}
  to {transform: translateY(294px);}
}

.ref {
  width: 800px;
  height: 600px;
  background-image: url(https://i.sstatic.net/Fx4bR.png);
  background-size: cover;
}
<div class="base">
<div class="element">
</div>
</div>
<div class="ref"></div>

The reference image utilized in this process can be viewed here:

https://i.sstatic.net/Fx4bR.png

Furthermore, two distinct timing functions are demonstrated in simulating the swing of a pendulum:

.test {
  height: 400px;
  width: 10px;
  background-color: lightgreen;
  display: inline-block;
  margin: 10px 100px;
  transform-origin: center top;
}

.anim1 {
   animation: oscil1 6s infinite; 
}

.anim2 {
   animation: oscil2 6s infinite; 
}


@keyframes oscil1 {
  from {transform: rotate(0deg); animation-timing-function: cubic-bezier(0.1, 0.3, 0.3, 1);}
  25% {transform: rotate(20deg); animation-timing-function: cubic-bezier(0.7, 0.0, 0.9, 0.7);}
  50% {transform: rotate(0deg); animation-timing-function: cubic-bezier(0.1, 0.3, 0.3, 1);}
  75% {transform: rotate(-20deg); animation-timing-function: cubic-bezier(0.7, 0.0, 0.9, 0.7);}
  to {transform: rotate(0deg);}
}

@keyframes oscil2 {
  from {transform: rotate(0deg); animation-timing-function: ease-out;}
  25% {transform: rotate(20deg); animation-timing-function: ease-in;}
  50% {transform: rotate(0deg); animation-timing-function: ease-out;}
  75% {transform: rotate(-20deg); animation-timing-function: ease-in;}
  to {transform: rotate(0deg);}
}
<div class="test anim1"></div>
<div class="test anim2"></div>

Answer №3

Using native JavaScript instead of CSS libraries, I've created a smooth transition effect using the Math.sin() method to tween values seamlessly. The code is minimal and produces a very fluid animation.

var img = document.querySelector( '.bellImg' ),
    start = 0;
function sine(){
  img.style.transform = "rotate(" + 20 * Math.sin( start ) + "deg)";
  start += 0.05;
  setTimeout(sine, 1000/30)
}
setTimeout( sine, 3000 );
.bellImg {
  height: 20px;
  width: 20px;
  position: absolute;
  left: 10px;
  top: 18px;
}
<img class="bellImg" src="https://cdn1.iconfinder.com/data/icons/freeline/32/bell_sound_notification_remind_reminder_ring_ringing_schedule-48.png">

I hope this explanation clears things up!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

A CSS-based puzzle game inspired by the classic game of Tetris

CLICK HERE TO ACCESS PLAYGROUND CHALLENGE To create a two-column div layout without any spaces using CSS is the challenge at hand. STARTING LAYOUT Each box is represented as: <div class=A1">A1</div> https://i.sstatic.net/FlZ8k.jpg DESIRED ...

Can a div be easily centered within its parent container?

In my coding project, I've encountered an issue with centering a #text div inside another #box div. My current attempt at centering it involves CSS code like this: #box { width: 50%; #text {position: relative; margin-left: 48%;} } While this cod ...

Steps for placing the table within the box

I am working on a website to enhance my CSS skills using templates. Currently, I am creating a table with 8 attributes. I am seeking advice on how to keep the table within the box borders without overflowing. I attempted to use overflow, but I believe I m ...

Guide to making a mobile number input field with a constant country code

I'm struggling to create a mobile input box that separates the mobile number and country code with a divider or fixes the country code in the field without success. Something similar to this image: https://i.sstatic.net/6HwYdZrB.png But I don't ...

The default action is not triggered when the click event occurs

Hey there, I have been working on this <ol> list of events using jQuery (version 1.4.2). Everything is set up within the $(document).ready() function. Something strange is happening where when I click on the <li>, it triggers a click on the co ...

Using Angular: How to access a component variable within a CSS file

I'm having issues with my css file and attempting to achieve the following: .login-wrapper { background-image: url({{imagePath}}); } Below is my component code: export class LoginComponent implements OnInit { imagePath: any = 'assets/discord ...

Sliding the container with a width adjustment and left margin fails to display all images

In the provided HTML code below, there is a functionality to move images horizontally by clicking on buttons: $(document).ready(function() { calculate_width(); $('#moveleft').click(function() { var loga = $('#marki #loga'); ...

Insert the dollar sign into a box for entering text

Is there a way to keep the dollar symbol at the beginning of a text box consistently across different browsers? The current code achieves this but with some issues in Firefox. How can I resolve this problem and improve the alignment of the dollar symbol us ...

What is the best way to align three divs side by side using HTML?

Hi there! I need help figuring out how to display 3 div elements inline with each other without resizing when the browser size changes. Can anyone provide some guidance on this? This is how it should look like: https://i.sstatic.net/pBRZr.png Code: bod ...

The footer navigation in CSS shifts position when it is hovered over

My attempt to add a <nav> at the footer was unsuccessful. The navigation bar in this fiddle is completely wrong as the entire nav moves when hovered, even though I did not include any animations. I want the items in the <nav> to stay fixed. ...

Showcasing data from Django models in a tabular format

I am new to django and seeking assistance. I have developed a website but it's not fully operational yet. The model I created looks like this; from django.db import models class InductiveSensors(models.Model): Part_No = models.CharField(max_lengt ...

What could be causing the child nodes of a particular class to not inherit the specified style?

When dynamically adding div child nodes to a search field, I do it like this: <div id="recommand" class="recommand"></div> data.result.forEach(function(entry) { let cont: rcommand; cont.username = entry[0]; const x = ...

Require triggering action upon hovering and reverting back to the original state after 7 seconds

I am working on a website that incorporates various animation effects. My goal is to activate a gif (animation) when hovered over and have it return to a static state once the cursor is moved away. When hovering, I want Image1 to disappear and reveal ...

Is it possible to utilize a single router link to run two forms concurrently within one setup?

I am trying to achieve a specific functionality with my two Angular forms. One form has just two fields, and when I click on one field, it should redirect me to the other form. The second form contains detailed information related to the field from the f ...

"Struggling with setting the default checked state for single and multiple checkboxes? The ng-init method with checkboxModel.value=true seems to be ineffective – any suggestions

<input type="checkbox" ng-model="isChecked.value" ng-true-value="'yes'" ng-false-value="'no'" ng-click='handleCheckboxChange(isChecked.value, idx);' ng-init="isChecked.value=true" /> ...

Changing badge colors dynamically in Bootstrap

I'm currently working on a simple web application using Django and Bootstrap 4.5. Within one of my models, there is a color attribute that I would like to visually represent using Bootstrap's badge. My goal is to dynamically set the badge's ...

Disabling border color for the hamburger icon in the navbar-toggler in Bootstrap with custom CSS

I've been struggling with this issue for a while now. I have searched and attempted various methods to remove the border color from the BS4 hamburger icon when it is clicked (it appears as Yellow on my local machine but in this snippet, it's blue ...

Guide on modifying CSS properties when hovering over the parent element

I am working with a table structure like this: <table> <tr><td class="momdad"><i class='glyphicon glyphicon-cog'></i> Hello </td><td> Mom </td></tr> <tr><td class="momdad">< ...

Need inspiration for testing web content with Protractor?

Exploring new possibilities for testing web content with Protractor. Any fresh ideas on what else can be tested? So far, I've checked links to ensure they redirect correctly. I've also verified text color and decoration changes when hovering ove ...

Restrict the number of items with PHP

I would like to limit the number of products displayed on a page, and when the limit is exceeded I want to show the additional products with a "more" button as a link. For example, if there are more than 6 products per page, it should display a "More" link ...