
This is a pure CSS, automatic rotating image carousel with 3D perspective effects and fade transitions.
It uses CSS3 transforms, animations, and custom properties to position & switch between images in a circular 3D space.
Features:
- 3D Perspective Transform: Uses CSS
perspectiveandtransform-style: preserve-3dto create depth and rotation effects. - Automatic Infinite Animation: Continuous Y-axis rotation powered by CSS keyframe animations with linear timing.
- Fade-In/Fade-Out Masks: Gradient masks on container edges create smooth visibility transitions as cards rotate.
- Custom Property Architecture: Uses CSS variables (
--n,--i,--w,--ba) for dynamic calculations based on image count. - Backface Culling: Implements
backface-visibility: hiddento prevent visual artifacts when cards rotate past 90 degrees. - Browser-Native Performance: Leverages GPU acceleration through CSS transforms for smooth 60fps animations.
See It In Action:
How to use it:
1. Create a container with the .scene class and nest a 3D transform wrapper with the .a3d class. Add your images as .card elements with the appropriate custom properties.
<!-- Main carousel container with perspective -->
<div class="scene">
<!-- 3D transform wrapper; --n defines total number of images -->
<div class="a3d" style="--n: 12">
<!-- Each card gets a unique --i index starting from 0 -->
<img class="card" src="image1.jpg" style="--i: 0"/>
<img class="card" src="image2.jpg" style="--i: 1"/>
<img class="card" src="image3.jpg" style="--i: 2"/>
<img class="card" src="image4.jpg" style="--i: 3"/>
... more images here
<img class="card" src="image12.jpg" style="--i: 11"/>
</div>
</div>2. The CSS handles three critical tasks: establishing 3D perspective, positioning cards in circular formation, and animating the rotation.
/* Base grid layout for both containers */
.scene, .a3d {
display: grid;
}
.scene {
/* Prevent horizontal scrollbars from rotated cards */
overflow: hidden;
/* Set 3D perspective distance; lower values = more extreme depth */
perspective: 35em;
/* Create fade effect on left/right edges using gradient mask */
mask: linear-gradient(90deg, #0000, red 20% 80%, #0000);
}
.a3d {
/* Center the carousel in the scene */
place-self: center;
/* Preserve 3D transforms for child elements */
transform-style: preserve-3d;
/* Rotate on Y-axis: 32s duration, linear timing, infinite loop */
animation: ry 32s linear infinite;
}
/* Y-axis rotation animation */
@keyframes ry {
to {
/* Rotate 360 degrees (1 full turn) */
rotate: y 1turn;
}
}
.card {
/* Base card width - adjust this to change carousel size */
--w: 17.5em;
/* Calculate angle between cards based on total count */
--ba: 1turn/var(--n);
/* Stack all cards in same grid cell for 3D positioning */
grid-area: 1/1;
/* Set card dimensions */
width: var(--w);
aspect-ratio: 7/10;
object-fit: cover;
border-radius: 1.5em;
/* Hide back face when card rotates past viewer */
backface-visibility: hidden;
/* Position each card in 3D space:
* 1. Rotate card to its position on circle (var(--i) * --ba)
* 2. Translate outward from center along Z-axis
* Distance calculated: (card_width/2 + gap) / tan(half_angle)
*/
transform:
rotatey(calc(var(--i) * var(--ba)))
translatez(calc(-1 * (0.5 * var(--w) + 0.5em) / tan(0.5 * var(--ba))));
}3. Configuration options:
--n(integer): Total number of images in the carousel. Set this on the.a3dcontainer. The value must match your actual image count for proper circular distribution.--i(integer): Index position for each card, starting from 0. Increment sequentially for each image. This determines the card’s rotational position in the circle.--w(CSS length): Base width of each card in the carousel. Default is17.5em. Adjust this value to scale the entire carousel size proportionally.--ba(CSS angle): Calculated base angle between consecutive cards. This is computed automatically as1turn/var(--n). Do not modify this manually.perspective(CSS length): Controls the intensity of the 3D effect on the.scenecontainer. Lower values create more dramatic depth. Default is35em.animationduration (time): Controls rotation speed on the.a3dcontainer. Default is32sfor one complete rotation. Decrease for faster speed.maskgradient stops (percentage): Adjusts fade-in/fade-out points on the.scenecontainer. Default is20%and80%. Move closer to center for longer fade zones.aspect-ratio(number): Sets card height relative to width. Default is7/10(portrait orientation). Use16/9for landscape cards.border-radius(CSS length): Rounds card corners. Default is1.5em. Set to0for sharp corners or50%for circular cards.
4. Animation customization:
/* Adjust rotation speed */
.a3d {
/* Faster rotation: reduce duration */
animation: ry 16s linear infinite;
/* Slower rotation: increase duration */
animation: ry 60s linear infinite;
/* Reverse direction: use negative rotation */
animation: ry-reverse 32s linear infinite;
}
@keyframes ry-reverse {
to {
rotate: y -1turn;
}
}
/* Pause on hover */
.a3d:hover {
animation-play-state: paused;
}5. Responsive sizing:
/* Mobile devices */
@media (max-width: 768px) {
.card {
--w: 12em;
}
.scene {
perspective: 25em;
}
}
/* Large screens */
@media (min-width: 1200px) {
.card {
--w: 20em;
}
.scene {
perspective: 45em;
}
}FAQs:
Q: How do I change the number of images in the carousel?
A: Update the --n value on the .a3d container to match your total image count. Then add or remove .card elements with sequential --i values starting from 0. The math adjusts automatically to redistribute cards evenly around the circle.
Q: Can I make the carousel rotate vertically instead of horizontally?
A: Yes. Change the animation from rotate: y 1turn to rotate: x 1turn in the keyframe. You’ll also need to adjust the initial card rotation from rotatey() to rotatex() in the transform chain. The trigonometric calculations remain the same.
Q: Why do some cards appear cut off or distorted on mobile devices?
A: This happens when the viewport is smaller than the carousel’s calculated radius. Reduce the --w card width value and lower the perspective distance in a media query.
Q: The rotation animation stutters or looks choppy. How do I fix this?
A: Enable GPU acceleration by adding will-change: transform to the .a3d container. Check that you’re not applying transforms to too many nested elements. If performance remains poor, reduce the number of cards or simplify the card content to decrease rendering complexity.






