
🔄 Object animation on scroll: step-by-step guide
You land on a site, scroll the page, and the logo in the corner smoothly rotates, reacting to every scroll wheel movement. It looks lively, tech-savvy, and adds that special touch that people notice.
Creating this rotation is simpler than it seems. You don't need heavy libraries or megabytes of animation frameworks. All you need is one div, a few lines of CSS, and literally one block of JavaScript.
Below is a step-by-step breakdown: from preparing the image to adapting for mobile screens. The code is tested, working, and has no dependencies.
💡 Quick overview:
- Prepare an SVG or PNG object and insert it into HTML
- Fix the position using
position: fixedand set offsets with viewport units - Add a JavaScript
scrollhandler that rotates the object usingtransform: rotate() - Adjust rotation speed by dividing
window.scrollYby a coefficient - For mobile, add a media query with new positioning and transition time
1. Preparing the object
The object can be anything: an icon, logo, vector shape, arrow symbol. I recommend SVG, it doesn't lose sharpness during transformations and weighs next to nothing. If transparency is critical and you don't have a vector, PNG will work.
Place the image inside a container div and put it in body, preferably near the top. A separate div simplifies management: later you can add neighboring elements or replace the image without touching the positioning logic.
1 <div id="scrollContainer"> 2 <img src="img/scrollObject.svg" alt="scrollObject" id="scrollObject"> 3 </div>
You can have multiple objects, just assign each one a unique id and duplicate the JS logic with binding to its own element. But for now, let's build one working instance.
2. Positioning with CSS
Where to place the object? Depends on the goal:
- Bottom right corner, a miniature icon that doesn't interfere with content. Classic for blogs and portfolios.
- Top left corner, scroll position indicator (progress wheel).
- Center of bottom edge, large decorative element drawing attention to the footer.
Let's look at the bottom right corner scenario, it's universal and doesn't cover navigation. In CSS, write:
1 #scrollObject { 2 position: fixed; 3 right: 3vw; 4 bottom: 3vh; 5 top: auto; 6 width: 7vh; 7 height: auto; 8 transition: 0.1s ease-out; 9 }
The key is position: fixed. This fixes the element relative to the browser window, not the document. When scrolling, content moves but the object stays in place. Units vw and vh (percentages of viewport width and height) automatically adjust offsets for any screen, from smartphone to 4K monitor.
The transition: 0.1s ease-out property smooths jerks when changing position. For rotation its role is secondary, but during resize or media query changes the transition looks cleaner.
3. Rotation animation with JavaScript
CSS can do simple @keyframes, but our task is to tie the rotation angle to scroll position. This is where JavaScript comes in. Minimal working code:
1 const scrollObject = document.getElementById("scrollObject"); 2 3 window.addEventListener("scroll", () => { 4 scrollObject.style.transform = `rotate(${window.scrollY}deg)`; 5 });
What's happening here: window.scrollY returns the number of pixels the page has scrolled vertically. This number is inserted into the CSS rotate() function, one pixel of scroll equals one degree of rotation.
If the object rotates too fast, divide the value by a coefficient:
1 const speedFactor = window.scrollY / 2; 2 const scrollObject = document.getElementById("scrollObject"); 3 4 window.addEventListener("scroll", () => { 5 scrollObject.style.transform = `rotate(${speedFactor}deg)`; 6 });
Pick a divisor that fits your page length. A value of /2 slows rotation by half, /4 makes it smooth and barely noticeable. For long landing pages use /6 or /8 so the object doesn't make ten full rotations by mid-page.
Performance note
The scroll event fires dozens of times per second. On a simple site with one transform this is unnoticeable, but if you have multiple animated objects or heavy DOM, wrap the logic in requestAnimationFrame:
1 const scrollObject = document.getElementById("scrollObject"); 2 let ticking = false; 3 4 window.addEventListener("scroll", () => { 5 if (!ticking) { 6 requestAnimationFrame(() => { 7 scrollObject.style.transform = `rotate(${window.scrollY}deg)`; 8 ticking = false; 9 }); 10 ticking = true; 11 } 12 });
requestAnimationFrame groups calls into browser sync frames (usually 60 fps) and doesn't run style recalculation on every scroll event, saving CPU and battery on mobile.
Also add will-change: transform to the #scrollObject CSS rule. This tells the browser to allocate a GPU compositing layer in advance, and rotation will be hardware-accelerated:
1 #scrollObject { 2 /* ... other properties ... */ 3 will-change: transform; 4 }
4. Mobile adaptation
On screens smaller than 600 pixels, the bottom right corner may conflict with burger menus or "back to top" buttons. A sensible solution is to move the object to the bottom center so it doesn't cover interactive elements:
1 @media only screen and (max-width: 600px) { 2 #scrollObject { 3 transition: 0.2s ease-out; 4 margin-left: -4vh; 5 width: 8vh; 6 height: auto; 7 top: 10px; 8 left: 50%; 9 } 10 }
The object moves to the top of the screen, centered using left: 50% with negative margin-left, a classic horizontal centering technique. Width is slightly increased (8vh instead of 7vh) to compensate for shorter viewing distance.
Note the transition: 0.2s ease-out in the media query. When switching from desktop to mobile (or during window resize), the object doesn't "jump" instantly but smoothly shifts to the new position over 0.2 seconds.

If you're working in Vue or React, extract the scroll object into a separate component. You can attach it to the root App.vue or in layout so the animation is present on all pages at once, without code duplication.

Modern alternative: CSS scroll-driven animations
In 2024-2025, Chromium browsers (Chrome, Edge, Opera) gained native support for animation-timeline: scroll(), scroll-linked animations without a single line of JavaScript. It looks like this:
1 @keyframes rotate { 2 to { transform: rotate(360deg); } 3 } 4 5 #scrollObject { 6 animation: rotate linear; 7 animation-timeline: scroll(); 8 }
Pro: zero JavaScript, con: only in Chromium for now (Firefox and Safari are implementing). For progressive enhancement you can combine: give the CSS version to modern browsers and keep the JS handler as a fallback for the rest. Details on MDN.
⁉️🤔 Frequently asked questions
Can I animate not just rotation, but also scale or opacity?
Yes.
transformsupports multiple functions at once. To make an object rotate and shrink on scroll:scrollObject.style.transform = \rotate(${window.scrollY}deg) scale(${1 - window.scrollY / 5000});. Opacity is handled separately fromtransformusingopacity. Combine up to five or six parameters, but test, multiple transformations on weak devices can cause micro-stutter. One or two parameters pluswill-change: transformis a safe ceiling for 60 fps.
Which is better: SVG or PNG for animation?
SVG, hands down. It scales without losing sharpness at any rotation and screen resolution, weighs less, and the browser renders it as vector (no pixel staircase on edges after
rotate). Take PNG only if you already have ready-made raster graphics and no time to redo it. Avoid JPEG for rotating objects, compression artifacts become more noticeable after rotation.
How do I make the animation start only when the object is in the visible area?
Use
IntersectionObserver. Instead of binding to globalscroll, you track whether the object entered the viewport and only then enable rotation. This saves resources when the element is off-screen. Basic template: create an observer withthreshold: 0, in the callback add/remove a.activeclass, and bind the JS scroll handler to the presence of this class.
Why does the animation stutter on my site?
Three typical causes. First: no
will-change: transform, the browser doesn't hand the element to GPU compositing and recalculates layout on every frame. Second: properties causing reflow (such as width, height, margin, or padding) are applied to the element inside the same scroll handler. Replace them withtransform: scale()andtransform: translate(). Third: the scroll callback executes withoutrequestAnimationFrameand spawns extra repaints between frames. Wrap the logic in the rAF pattern (example in the "Performance note" section).
Is JavaScript required? There are pure CSS solutions.
CSS
@keyframesare tied to time, not scroll position. Yes,animation-timeline: scroll()changes the game, but browser support is still incomplete (see section 4). As of mid-2026, the JavaScript solution withscrollY+transformis still the most reliable cross-browser way to tie rotation to scroll. The entire code fits in 5 lines, pulls no dependencies, and works from Internet Explorer 10 to fresh Chrome.
Should you add scroll animation to your site?
Rotating an object on scroll is not just a "cool trick". It's a micro-interaction that gives the visitor tactile feedback: "I'm moving the page and something is happening". Properly configured, it doesn't irritate or distract, but adds personality to the site.
If the site is minimalist, limit yourself to one small object in the corner and a coefficient of /4 or /6. For landing pages and portfolios you can play bolder: a large central element with /2, combined rotation and scale animation, image change when reaching a certain scrollY.
Main rule: scroll animation should complement content, not compete with it for attention. One object, smooth movement, no intrusiveness, and your site will be remembered.



