22 CSS Image Galleries19 / 22
CSS Infinite Scroll Image Strip
A seamless, endlessly looping horizontal banner of images that scrolls forever with no visible seam or reset — the marquee/logo-wall effect brand pages and 'as featured in' rows search for. Pure CSS keyframes.
Published Updated
The code
<section class="gl-18" aria-label="Infinite scrolling image strip">
<div class="gl-18__viewport">
<div class="gl-18__track">
<img src="https://loremflickr.com/300/300/architecture,mono?lock=1801" alt="Gallery image 1" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/city,mono?lock=1802" alt="Gallery image 2" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/nature,mono?lock=1803" alt="Gallery image 3" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/portrait,mono?lock=1804" alt="Gallery image 4" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/street,mono?lock=1805" alt="Gallery image 5" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/mountain,mono?lock=1806" alt="Gallery image 6" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/architecture,mono?lock=1801" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/city,mono?lock=1802" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/nature,mono?lock=1803" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/portrait,mono?lock=1804" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/street,mono?lock=1805" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/mountain,mono?lock=1806" alt="" aria-hidden="true" loading="lazy" decoding="async">
</div>
</div>
</section><section class="gl-18" aria-label="Infinite scrolling image strip">
<div class="gl-18__viewport">
<div class="gl-18__track">
<img src="https://loremflickr.com/300/300/architecture,mono?lock=1801" alt="Gallery image 1" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/city,mono?lock=1802" alt="Gallery image 2" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/nature,mono?lock=1803" alt="Gallery image 3" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/portrait,mono?lock=1804" alt="Gallery image 4" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/street,mono?lock=1805" alt="Gallery image 5" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/mountain,mono?lock=1806" alt="Gallery image 6" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/architecture,mono?lock=1801" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/city,mono?lock=1802" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/nature,mono?lock=1803" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/portrait,mono?lock=1804" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/street,mono?lock=1805" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/mountain,mono?lock=1806" alt="" aria-hidden="true" loading="lazy" decoding="async">
</div>
</div>
</section>.gl-18,
.gl-18 *,
.gl-18 *::before,
.gl-18 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.gl-18 ::selection {
background: oklch(0.7 0.14 30);
color: #111;
}
.gl-18 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
padding: 40px 0;
background: #0f0f0f;
display: flex;
align-items: center;
justify-content: center;
}
.gl-18__viewport {
width: 100%;
overflow: hidden;
-webkit-mask-image: linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
mask-image: linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
}
.gl-18__track {
display: flex;
gap: 16px;
width: max-content;
animation: gl-18-marquee 30s linear infinite;
}
.gl-18__viewport:hover .gl-18__track {
animation-play-state: paused;
}
.gl-18__track img {
display: block;
width: 220px;
height: 220px;
object-fit: cover;
border-radius: 12px;
flex: 0 0 auto;
filter: grayscale(1) contrast(1.05);
transition: filter .4s;
}
.gl-18__track img:hover {
filter: none;
}
@keyframes gl-18-marquee {
to {
transform: translateX(-50%);
}
}
@media (prefers-reduced-motion: reduce) {
.gl-18__track {
animation: none;
}
}.gl-18,
.gl-18 *,
.gl-18 *::before,
.gl-18 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.gl-18 ::selection {
background: oklch(0.7 0.14 30);
color: #111;
}
.gl-18 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
padding: 40px 0;
background: #0f0f0f;
display: flex;
align-items: center;
justify-content: center;
}
.gl-18__viewport {
width: 100%;
overflow: hidden;
-webkit-mask-image: linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
mask-image: linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
}
.gl-18__track {
display: flex;
gap: 16px;
width: max-content;
animation: gl-18-marquee 30s linear infinite;
}
.gl-18__viewport:hover .gl-18__track {
animation-play-state: paused;
}
.gl-18__track img {
display: block;
width: 220px;
height: 220px;
object-fit: cover;
border-radius: 12px;
flex: 0 0 auto;
filter: grayscale(1) contrast(1.05);
transition: filter .4s;
}
.gl-18__track img:hover {
filter: none;
}
@keyframes gl-18-marquee {
to {
transform: translateX(-50%);
}
}
@media (prefers-reduced-motion: reduce) {
.gl-18__track {
animation: none;
}
}/* No JavaScript — a doubled track translated -50% loops seamlessly and forever. */
/* No JavaScript — a doubled track translated -50% loops seamlessly and forever. */Here's a working CSS Image Gallerie from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Infinite Scroll Image Strip
Source: https://codefronts.com/snippets/css-image-gallery/css-infinite-scroll-image-strip/
A seamless, endlessly looping horizontal banner of images that scrolls forever with no visible seam or reset — the marquee/logo-wall effect brand pages and 'as featured in' rows search for. Pure CSS keyframes.
## HTML
```html
<section class="gl-18" aria-label="Infinite scrolling image strip">
<div class="gl-18__viewport">
<div class="gl-18__track">
<img src="https://loremflickr.com/300/300/architecture,mono?lock=1801" alt="Gallery image 1" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/city,mono?lock=1802" alt="Gallery image 2" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/nature,mono?lock=1803" alt="Gallery image 3" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/portrait,mono?lock=1804" alt="Gallery image 4" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/street,mono?lock=1805" alt="Gallery image 5" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/mountain,mono?lock=1806" alt="Gallery image 6" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/architecture,mono?lock=1801" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/city,mono?lock=1802" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/nature,mono?lock=1803" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/portrait,mono?lock=1804" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/street,mono?lock=1805" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/mountain,mono?lock=1806" alt="" aria-hidden="true" loading="lazy" decoding="async">
</div>
</div>
</section>
```
## CSS
```css
.gl-18,
.gl-18 *,
.gl-18 *::before,
.gl-18 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.gl-18 ::selection {
background: oklch(0.7 0.14 30);
color: #111;
}
.gl-18 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
padding: 40px 0;
background: #0f0f0f;
display: flex;
align-items: center;
justify-content: center;
}
.gl-18__viewport {
width: 100%;
overflow: hidden;
-webkit-mask-image: linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
mask-image: linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
}
.gl-18__track {
display: flex;
gap: 16px;
width: max-content;
animation: gl-18-marquee 30s linear infinite;
}
.gl-18__viewport:hover .gl-18__track {
animation-play-state: paused;
}
.gl-18__track img {
display: block;
width: 220px;
height: 220px;
object-fit: cover;
border-radius: 12px;
flex: 0 0 auto;
filter: grayscale(1) contrast(1.05);
transition: filter .4s;
}
.gl-18__track img:hover {
filter: none;
}
@keyframes gl-18-marquee {
to {
transform: translateX(-50%);
}
}
@media (prefers-reduced-motion: reduce) {
.gl-18__track {
animation: none;
}
}
```
## JavaScript
```js
/* No JavaScript — a doubled track translated -50% loops seamlessly and forever. */
```Here's a working CSS Image Gallerie from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Infinite Scroll Image Strip
Source: https://codefronts.com/snippets/css-image-gallery/css-infinite-scroll-image-strip/
A seamless, endlessly looping horizontal banner of images that scrolls forever with no visible seam or reset — the marquee/logo-wall effect brand pages and 'as featured in' rows search for. Pure CSS keyframes.
## HTML
```html
<section class="gl-18" aria-label="Infinite scrolling image strip">
<div class="gl-18__viewport">
<div class="gl-18__track">
<img src="https://loremflickr.com/300/300/architecture,mono?lock=1801" alt="Gallery image 1" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/city,mono?lock=1802" alt="Gallery image 2" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/nature,mono?lock=1803" alt="Gallery image 3" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/portrait,mono?lock=1804" alt="Gallery image 4" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/street,mono?lock=1805" alt="Gallery image 5" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/mountain,mono?lock=1806" alt="Gallery image 6" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/architecture,mono?lock=1801" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/city,mono?lock=1802" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/nature,mono?lock=1803" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/portrait,mono?lock=1804" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/street,mono?lock=1805" alt="" aria-hidden="true" loading="lazy" decoding="async">
<img src="https://loremflickr.com/300/300/mountain,mono?lock=1806" alt="" aria-hidden="true" loading="lazy" decoding="async">
</div>
</div>
</section>
```
## CSS
```css
.gl-18,
.gl-18 *,
.gl-18 *::before,
.gl-18 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.gl-18 ::selection {
background: oklch(0.7 0.14 30);
color: #111;
}
.gl-18 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
padding: 40px 0;
background: #0f0f0f;
display: flex;
align-items: center;
justify-content: center;
}
.gl-18__viewport {
width: 100%;
overflow: hidden;
-webkit-mask-image: linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
mask-image: linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
}
.gl-18__track {
display: flex;
gap: 16px;
width: max-content;
animation: gl-18-marquee 30s linear infinite;
}
.gl-18__viewport:hover .gl-18__track {
animation-play-state: paused;
}
.gl-18__track img {
display: block;
width: 220px;
height: 220px;
object-fit: cover;
border-radius: 12px;
flex: 0 0 auto;
filter: grayscale(1) contrast(1.05);
transition: filter .4s;
}
.gl-18__track img:hover {
filter: none;
}
@keyframes gl-18-marquee {
to {
transform: translateX(-50%);
}
}
@media (prefers-reduced-motion: reduce) {
.gl-18__track {
animation: none;
}
}
```
## JavaScript
```js
/* No JavaScript — a doubled track translated -50% loops seamlessly and forever. */
```How this works
The track holds the image set twice back-to-back, then animates transform: translateX() from 0 to -50% on an infinite linear loop. Because the second half is an exact copy of the first, the moment the animation completes a cycle the strip is visually identical to its start — so the loop is seamless with no jump.
The strip pauses on hover so users can inspect an image, edge-fade masks soften the entry/exit, and prefers-reduced-motion stops the marquee entirely (a common accessibility requirement for auto-scrolling content).
Make it yours
- Set speed with the animation
duration; longer = slower drift. - Reverse direction by animating to
+50%or usinganimation-direction: reverse. - Duplicate a second row scrolling the opposite way for a woven look.
- Widen the edge-fade mask for a softer or harder vignette.
Gotchas — read before shipping
- The content must be duplicated exactly and the translate must be
-50%of the doubled track, or the loop visibly jumps. - Always provide a reduced-motion stop — perpetual auto-motion is an accessibility and INP concern.
- Mark the duplicated copy
aria-hidden="true"so screen readers don't announce every image twice.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 113+ | 111+ |
Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 43+.
Keyframe translate + mask edge-fade — universally supported (mask may need -webkit- in older Safari).