Infinite Marquee
CSS Scroll Animation
Marquee achieve the horizontal scrolling animation of blocks.
The general procedures are as follows:
- Create blocks of content (e.x., logos) to be animated inside a wrapper.
- Make position of blocks absoulte, so that all these blocks are stacked on top of each other.
- Animate each block from left to right ( or right to left)
- Assign different time delay to animation for each block to achieve horizontal scrolling.
Step 1
Create a wrapper class called “marquee__inner”
Then, create the block holder. Here, I use span combinning with display as inline blocks and position absolte to achive our desired outcome. (You can choose to use div or others as place holder of your choice. )
1
2
3
4
5
6
7
8
9
10
11
12
.marquee__inner{
position:relative
}
.marquee__inner span{
background-color: blue;
width: 100px;
height: 100px;
display: inline-block;
position: absolute;
}
Update the css for visibility only.
1
2
3
4
5
6
.marquee__inner{
position:relative;
border: 1px solid red;
height: 100px;
margin-top: 5rem;
}
Restrict the widith of the scrolling area to 90% or max 1536px.
1
2
3
4
5
6
7
8
.marquee__inner{
position:relative;
border: 1px solid red;
height: 100px;
margin: 5rem auto;
width: 90%;
max-width: 1536px;
}
Next we add left:100% to postion all blocks outside wrapper on the right, which will be our animation starts.
Then add animation key frames
1
2
3
4
5
@keyframes scrollLeft{
to {
left: -100px;
}
}
Explaination of the position of the palceholder. By set left 100% relates to the parent wrapper, the span block is start right at the end of the wrapper on the right. And set
Warnning: we may want to use right:100% instead of left: -100px, which will position the block to the left of wrapper. But we cannot animate beacuse of property mismatch.
Currently, we have achive animation of span blocks from right to left, but all blocks are on top each other. Next, we will give unique animation delay for each span block.
The following are the animation delay formula
1
animation-delay: calc(var(--animation-duration) / var(--total-blocks) * (n - 1));
Note CSS does not allow direct calculations inside nth-child(n), you must manually specify each delay like this:
1
2
3
4
5
6
/* Assign unique animation delays manually */
.marquee__inner span:nth-child(1) { animation-delay: calc(var(--animation-duration) / var(--total-blocks) * 0); }
.marquee__inner span:nth-child(2) { animation-delay: calc(var(--animation-duration) / var(--total-blocks) * 1); }
.marquee__inner span:nth-child(3) { animation-delay: calc(var(--animation-duration) / var(--total-blocks) * 2); }
.marquee__inner span:nth-child(4) { animation-delay: calc(var(--animation-duration) / var(--total-blocks) * 3); }
/* ... Continue until nth-child(20) */
Here are the updated code in css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
:root {
--animation-duration: 10s; /* Define a time variable */
--total_blocks: 10; /* Define the total number of blocks */
}
.marquee__inner{
position:relative;
border: 1px solid red;
height: 100px;
margin: 5rem auto;
width: 90%;
max-width: 1536px;
}
.marquee__inner span{
background-color: blue;
width: 100px;
height: 100px;
display: inline-block;
position: absolute;
left: 100%;
/* right: 100%; */
animation-name: scrollLeft;
animation-duration: var(--animation-duration);
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes scrollLeft{
to {
left: -100px;
}
}
.marquee__inner span:nth-child(1) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 0); }
.marquee__inner span:nth-child(2) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 1); }
.marquee__inner span:nth-child(3) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 2); }
.marquee__inner span:nth-child(4) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 3); }
.marquee__inner span:nth-child(5) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 4); }
.marquee__inner span:nth-child(6) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 5); }
.marquee__inner span:nth-child(7) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 6); }
.marquee__inner span:nth-child(8) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 7); }
.marquee__inner span:nth-child(9) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 8); }
.marquee__inner span:nth-child(10) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * 9); }
However, there is a problem that when the page is loaded for the first time, the left side is most empty since the blocks are just start to move left.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.marquee__inner span:nth-child(1) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -0); }
.marquee__inner span:nth-child(2) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -1); }
.marquee__inner span:nth-child(3) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -2); }
.marquee__inner span:nth-child(4) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -3); }
.marquee__inner span:nth-child(5) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -4); }
.marquee__inner span:nth-child(6) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -5); }
.marquee__inner span:nth-child(7) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -6); }
.marquee__inner span:nth-child(8) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -7); }
.marquee__inner span:nth-child(9) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -8); }
.marquee__inner span:nth-child(10) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -9); }
To solve this issue, we time the time with -1.
As you can see, there is a lot of repetition, however, we can use javascript to simplify it.
Next, we will hide the block outside the wrapper by adding overflow hidden to the wrapper.
1
2
3
4
5
6
7
8
9
.marquee__inner{
position:relative;
border: 1px solid red;
height: 100px;
margin: 5rem auto;
width: 90%;
max-width: 1536px;
overflow: hidden;
}
Next. we add mask-image property to have fake effect on the both end of the wrapper.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.marquee__inner{
position:relative;
border: 1px solid red;
height: 100px;
margin: 5rem auto;
width: 90%;
max-width: 1536px;
overflow: hidden;
mask-image: linear-gradient(
to right,
transparent,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 80%,
rgba(0, 0, 0, 0)
);
}
However, when screen is small, the blocks are touching each other.
Next, our goal is to make it responsive by modify the left property to left: calc(100px * var(–total_blocks));
1
2
3
4
5
6
7
8
9
10
11
12
13
.marquee__inner span{
background-color: blue;
width: 100px;
height: 100px;
display: inline-block;
position: absolute;
left: calc(100px * var(--total_blocks));
/* right: 100%; */
animation-name: scrollLeft;
animation-duration: var(--animation-duration);
animation-timing-function: linear;
animation-iteration-count: infinite;
}
The modfication works for small screen, but not for large screen.
1
left: max(100%, calc(100px * var(--total_blocks)));
The new modification solve the issue on both small and large screen by using max.
Here is the complete code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
:root {
--animation-duration: 10s; /* Define a time variable */
--total_blocks: 10; /* Define the total number of blocks */
}
.marquee__inner{
position:relative;
border: 1px solid red;
height: 100px;
margin: 5rem auto;
width: 90%;
max-width: 1536px;
overflow: hidden;
mask-image: linear-gradient(
to right,
transparent,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 80%,
rgba(0, 0, 0, 0)
);
}
.marquee__inner span{
background-color: blue;
width: 100px;
height: 100px;
display: inline-block;
position: absolute;
left: max(100%, calc(100px * var(--total_blocks)));
/* right: 100%; */
animation-name: scrollLeft;
animation-duration: var(--animation-duration);
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes scrollLeft{
to {
left: -100px;
}
}
.marquee__inner span:nth-child(1) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -0); }
.marquee__inner span:nth-child(2) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -1); }
.marquee__inner span:nth-child(3) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -2); }
.marquee__inner span:nth-child(4) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -3); }
.marquee__inner span:nth-child(5) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -4); }
.marquee__inner span:nth-child(6) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -5); }
.marquee__inner span:nth-child(7) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -6); }
.marquee__inner span:nth-child(8) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -7); }
.marquee__inner span:nth-child(9) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -8); }
.marquee__inner span:nth-child(10) {
animation-delay: calc(var(--animation-duration) / var(--total_blocks) * -9); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infinite Marquee Effect</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="marquee">
<div class="marquee__inner">
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
<span>6</span>
<span>7</span>
<span>8</span>
<span>9</span>
<span>10</span>
</div>
</div>
</body>
</html>