Animations have a significant affect on the impression an internet site offers. On this article, we introduce 4 transition results that easily swap full-screen pictures.
Utilizing GSAP, ScrollTrigger, and SVG masks, we’ll implement scroll-synchronized animations with grid and blind-style buildings. Reasonably than merely altering pictures, the objective is to offer intention to the scene transitions themselves, creating transitions that convey the positioning’s info and environment extra naturally.
Frequent markup construction (HTML)
Lets take a look at the inspiration utilized in all 4 demos.
<part class="stage">
<div class="layers">
<svg class="layer" viewBox="0 0 100 100" preserveAspectRatio="none">
<defs>
<masks id="mask1" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100" top="100" fill="black"/>
<g id="blinds1"></g>
</masks>
</defs>
<picture href="img/pic-1.jpg"
x="0" y="0" width="100" top="100"
preserveAspectRatio="xMidYMid slice"
masks="url(#mask1)"/>
</svg>
<svg class="layer" viewBox="0 0 100 100" preserveAspectRatio="none">
<defs>
<masks id="mask2" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100" top="100" fill="black"/>
<g id="blinds2"></g>
</masks>
</defs>
<picture href="img/pic-3.jpg"
x="0" y="0" width="100" top="100"
preserveAspectRatio="xMidYMid slice"
masks="url(#mask2)"/>
</svg>
<svg class="layer" viewBox="0 0 100 100" preserveAspectRatio="none">
<defs>
<masks id="mask3" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100" top="100" fill="black"/>
<g id="blinds3"></g>
</masks>
</defs>
<picture href="img/pic-2.jpg"
x="0" y="0" width="100" top="100"
preserveAspectRatio="xMidYMid slice"
masks="url(#mask3)"/>
</svg>
<div class="progress-bar">
<div class="section">
<div class="fill"></div>
</div>
<div class="section">
<div class="fill"></div>
</div>
<div class="section">
<div class="fill"></div>
</div>
</div>
<div class="texts">
<div class="txt">
<h1>FIRST<br>
IMAGE</h1>
<h2>Part transition</h2>
<span>Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content</span></div>
<div class="txt">
<h1>SECOND<br>
IMAGE</h1>
<h2>Part transition</h2>
<span>Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content</span></div>
<div class="txt">
<h1>THIRD<br>
IMAGE</h1>
<h2>Part transition</h2>
<span>Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content Textual content</span></div>
</div>
</div>
</part>
Responsive SVG canvas
Set viewBox="0 0 100 100" on .layer (the SVG aspect) and handle inside coordinates utilizing digital models (0–100). This enables the masks calculation logic to stay constant even when the display screen measurement adjustments.
Defining the SVG Masks
Outline a singular <masks> for every layer.
Base (black): Place a rectangle with fill="#000000" to set the preliminary state to “utterly hidden.”
Dynamic elements (white): Put together an empty <g id="blinds*"> the place white rectangles generated by JavaScript might be inserted. Solely the areas the place this “white” overlaps develop into seen.
The picture aspect as content material
That is the primary aspect that will get masked. It’s linked to the masks above utilizing the masks="url(#masks*)" attribute.
preserveAspectRatio="xMidYMid slice": Recreates habits just like CSS object-fit: cowl inside SVG, stopping picture distortion whereas overlaying all the display screen.
Textual content and progress bar
Components that visually complement the animation’s state and environment.
Core Logic (Shared JavaScript)
Easy interplay (Lenis)
Converts normal scrolling into movement with inertia. When mixed with scrub, it creates a clean, responsive really feel because the masks follows the scroll.
Dynamic format calculation (updateLayout)
The width is dynamically calculated (vbWidth) primarily based on the display screen top (100). On resize, the present timeline is kill()ed and rebuilt, making certain the pictures proceed to cowl the total display screen with out distortion whatever the display screen ratio.
Excessive-precision synchronization (ScrollTrigger)
The general progress is synchronized with scrub: 2.0–2.5. Even after scrolling stops, the animation continues barely, including a refined and nice trailing movement.
Hole prevention (Anti-aliasing)
Along with utilizing shape-rendering="crispEdges", small overlaps (about +0.01 to +0.1) are added relying on every impact’s calculation logic. This visually eliminates the tiny 1px gaps that may seem between segmented rectangles.
Animation Variations
This part explains the core of every demo: the JavaScript logic used to generate and animate the rectangles.
1. Horizontal Blinds
An impact the place horizontal traces develop vertically, regularly filling the display screen.
1. Coordinate Calculation Logic (Placement)
Contained in the createBlinds operate, the display screen top (vbHeight) is evenly divided by BLIND_COUNT (30 traces), and the heart line (centerY) the place every line ought to be positioned is calculated.
const h = vbHeight / BLIND_COUNT; // Last top of a single blind slit
// ...inside loop...
// Calculate the middle Y coordinate for every slit.
// Subtracting from vbHeight flips the order, stacking them from backside to prime.
const centerY = vbHeight - (currentY + h / 2);
The important thing level right here is that the traces are not merely positioned from prime to backside. As an alternative, by subtracting from vbHeight, a reversed stacking calculation is carried out in order that the traces accumulate from the underside upward. This course of causes the blinds to be positioned ranging from the underside of the display screen and shifting towards the highest.
Moreover, for every slit, two rectangles—rectTop and rectBottom—are positioned on the very same centerY, overlapping one another. Since their preliminary top is 0, nothing is seen at the beginning.
2. Animation Mechanism
When the openBlinds operate runs, the 2 overlapping rectangles start shifting in reverse vertical instructions.
y: (i) => {
const b = blinds[Math.floor(i / 2)];
// Even index strikes as much as reveal, odd index stays on the heart line
return i % 2 === 0 ? b.y - b.h : b.y;
},
top: (i) => {
const b = blinds[Math.floor(i / 2)];
// Increase top to full measurement with a tiny offset (0.01) to remove gaps
return b.h + 0.01;
}
rectTop (when i is even): whereas extending its top, its y coordinate strikes upward by half of its personal top (b.h).
rectBottom (when i is odd): its y coordinate stays unchanged whereas its top extends downward from the middle.
This contrasting movement—one shifting upward whereas the opposite expands in place—creates the visible impact of opening each upward and downward from the middle line.
3. The “0.01” that eliminates gaps
The b.h + 0.01 within the code performs an vital function.
In SVG rendering, even when shapes are mathematically positioned precisely subsequent to one another, browser calculations involving subpixels can typically produce tiny gaps of lower than 1px. By deliberately growing the scale by 0.01 so adjoining components overlap barely, the fill stays seamless at any decision.
4. Staggered timing for visible impact
stagger: { every: 0.02, from: "begin" }
Not all the traces open on the identical time. As an alternative, they react with a 0.02-second offset.
The vital level right here is that the animation targets are ordered like this:[top1, bottom1, top2, bottom2, ...]
Consequently, the highest and backside pair of the identical slit transfer virtually concurrently, then the animation proceeds to the subsequent slit in sequence. In different phrases, the blinds open one after the other from the underside upward.
This refined time offset creates a rhythmic, steady flap-flap-flap… unfolding movement rising from the underside of the display screen, including a satisfying sense of response to the scroll interplay.
2. Random Grid
The display screen is split right into a grid the place panels open at completely different timings, making a digital but natural visible impact.
1. Coordinate Calculation Logic (Placement)
Contained in the createBlinds operate, the variety of columns (cols) is set for every system primarily based on the display screen width, and the variety of rows (rows) is then mechanically calculated to match the display screen’s side ratio.
// ------------------
// Grid configuration
// ------------------
const cols = getGridCols(); // 14 (PC) / 10 (Pill) / 6 (SP)
const rows = GRID_ROWS || Math.spherical(cols * (vbHeight / vbWidth));
// ------------------
// Cell dimensions
// ------------------
const cellW = vbWidth / cols;
const cellH = vbHeight / rows;
The important thing level right here is that the rows are not calculated merely from the display screen ratio alone. As an alternative, the calculation makes use of the variety of columns (cols) because the reference in order that the cell sizes stay as at the same time as potential each vertically and horizontally.
The components cols * (vbHeight / vbWidth) adjusts the vertical division depend by multiplying the horizontal division density by the display screen’s side ratio. This retains the grid balanced in each instructions.
Consequently, even when the variety of columns adjustments relying on the system, the cells don’t develop into excessively tall or broad, and their proportions keep near sq..
Utilizing a double for loop, rectangles with the calculated cellW (width) and cellH (top) are laid out like tiles. Within the preliminary state, opacity: 0 is utilized, so all the things begins utterly hidden.
2. Animation Mechanism
Within the openBlinds operate, random movement is utilized to all generated rectangles (cells) utilizing GSAP utilities.
operate openBlinds(cells) {
// Shuffle cells to create a random reveal impact
const shuffled = gsap.utils.shuffle([...cells]);
return gsap.timeline()
.to(shuffled, {
opacity: 1,
period: 1,
ease: "power3.out",
stagger: {
every: 0.02
}
});
}
After shuffling the panel order utilizing gsap.utils.shuffle, the opacity is returned to 1 with a 0.02-second stagger. This creates a visually attention-grabbing impact the place the grid itself stays orderly, however the best way it seems is irregular.
3. Machine Optimization (Responsive)
On this impact, the grid density adjustments dynamically relying on the display screen measurement.
PC: 14 columns
Cell: 6 columns
When the display screen is resized, updateLayout runs and redraws the grid utilizing the optimum variety of cells primarily based on the newest display screen measurement. This ensures the meant degree of immersion is maintained on any system.
4. Consideration to Visible High quality
As with Horizontal Blinds, shape-rendering="crispEdges" is specified.
This suppresses edge blurring brought on by anti-aliasing and forces the panel edges to render nearer to pixel boundaries.
Consequently, the boundaries between adjoining cells seem sharper, giving all the grid a cleaner and extra strong impression.
As a result of SVG rendering typically includes scaling, subpixel calculations can typically trigger slight blurring alongside edges. By specifying crispEdges, this visible “softness” is minimized, stopping small gaps the place the background colour would possibly in any other case seem between cells.
3. Vertical Blinds
An impact the place vertical strips develop horizontally, filling the display screen back and forth. It creates a well-recognized but refined transition, just like curtains or blinds opening and shutting.
1. Coordinate Calculation Logic (Placement)
Contained in the createBlinds operate, the display screen width (vbWidth) is evenly divided by BLIND_COUNT (12 traces) to calculate the width of every strip (w). Two rectangles (rectLeft, rectRight) are then positioned overlapping one another on the middle line (centerX) of every strip.
const w = vbWidth / BLIND_COUNT; // Last width of every vertical slit
// ...inside loop...
// Calculate the middle X coordinate for every slit to develop from.
const centerX = currentX + w / 2;
For the reason that preliminary width is ready to 0, nothing is seen contained in the masks at the beginning. Whereas the horizontal blinds divide and management the top, this model divides and controls the width.
Preliminary layer
On this model, isFirstLayer is used as a result of the primary picture is meant to be seen from the start. Within the different patterns, the animation begins from the primary picture as nicely, however on this model solely the primary layer is proven initially to help circumstances the place the visible must be established earlier than scrolling begins. This isn’t a structural requirement, however a alternative made for expressive functions.
2. Animation Mechanism
With the openBlinds operate, the 2 rectangles that overlap on the heart develop their width whereas sliding outward to the left and proper.
x: (i) => Odd index: stays on the heart line
return i % 2 === 0 ? b.x - b.w : b.x;
,
width: (i) => {
const b = blinds[Math.floor(i / 2)];
// Increase width to full measurement with a 0.05 offset to forestall hairline gaps
return b.w + 0.05;
}
Why Math.flooring(i / 2) is used
In GSAP’s attr animation, the array is handed within the following order:[b0.left, b0.right, b1.left, b1.right, ...]
This implies each two components signify one set (one blind). By dividing the index i by 2 and rounding down, the code can appropriately decide which blind (0th, 1st, and so on.) the present rect belongs to.
Enlargement logic
An SVG rect usually grows to the proper from its top-left origin.
rectLeft (even index): By shifting the x coordinate to b.x - b.w (to the left) whereas increasing its width, it visually seems to increase towards the left.
rectRight (odd index): The x coordinate stays mounted on the heart (b.x) whereas the width expands, naturally extending to the proper.
By combining this motion and scaling, the animation creates a movement the place a wiper-like form expands outward from the middle line to either side.
3. Hole prevention and format robustness
By including a small worth in width: b.w + 0.05, every strip barely overlaps with its neighbors. This prevents 1px gaps brought on by subpixel calculations.
Impact of crispEdges
r.setAttribute("shape-rendering", "crispEdges"); suppresses anti-aliasing and prevents edge blurring. For designs dominated by straight traces—like blinds—this setting produces extraordinarily sharp edges and reduces visible noise.
4. Side-ratio dealing with logic
In updateLayout, vbWidth is recalculated dynamically in response to the display screen ratio.
Peak reference: vbHeight is mounted at 100
Width calculation: (width / top) * 100
This calculates the relative width when the peak is normalized to 100. With this technique, the 12 strips stay evenly divided whatever the window ratio, making certain the display screen is all the time utterly crammed in a responsive format.
5. Continuity via stagger
stagger: { every: 0.02, from: "begin" } is utilized.
As a result of pairs (left, proper) are organized consecutively within the array, a slight timing offset happens between every pair. Consequently, every strip seems to open sequentially from the left aspect towards the proper.
As scrolling progresses, the picture is revealed easily from left to proper, creating a nice visible rhythm.
4. Column Random Grid
Probably the most intricate and dynamic impact, combining directionality and randomness inside a grid construction. It delivers a richly layered visible expertise, the place a wave-like movement sweeps from left to proper whereas irregular panel openings interweave all through.
1. Coordinate Calculation Logic (Placement)
The bottom grid technology is identical as in 02. Random Grid, however the construction is designed in order that it will possibly exactly observe which column every cell belongs to for animation management.
// ------------------
// Grid configuration
// ------------------
const cols = getGridCols(); // Adaptive columns: 14 (PC) / 10 (Pill) / 6 (SP)
// Calculate rows primarily based on side ratio to maintain cells as sq. as potential
const rows = GRID_ROWS || Math.spherical(cols * (vbHeight / vbWidth));
// ------------------
// Cell dimensions
// ------------------
const cellW = vbWidth / cols;
const cellH = vbHeight / rows;
// Generate all cells through nested loops and retailer in a flat array (row-major order)
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
// Rectangle technology logic...
}
}
Intent behind the row calculation (aspect-ratio preservation logic)
const rows = GRID_ROWS || Math.spherical(cols * (vbHeight / vbWidth));
This expression does greater than merely calculate the variety of rows mechanically. By sustaining the connection cols : rows ≒ vbWidth : vbHeight, it prevents every cell from turning into excessively tall or broad. In different phrases, this logic makes use of the variety of columns as a reference, multiplies it by the viewBox side ratio, and generates cells which are as near sq. as potential. This can be a essential design level for preserving each grid density and visible stability in responsive environments.
Concerning the array construction (row-major flat array)
As a result of the cells are generated within the order for (let y...) { for (let x...) {, the cells array turns into a flat construction organized row by row (row-major order).
[ row0-col0, row0-col1, ... row0-colN, row1-col0, row1-col1, ... ]
The later column-extraction logic is constructed on prime of this construction.
2. Animation Mechanism (Column-based management)
The core of this impact lies within the reconstruction of the array contained in the openBlinds operate. Reasonably than making use of randomness throughout all the grid, it reorders the weather whereas treating every column as a definite group.
operate openBlinds({ cells, rows, cols }) {
const ordered = [];
// Reconstruct the flat array into columns
for (let x = 0; x < cols; x++) {
const column = [];
for (let y = 0; y < rows; y++) {
// Extract cell index from the unique row-major array
const index = y * cols + x;
column.push(cells[index]);
}
// Randomize the order of cells WITHIN the particular column
const shuffledColumn = gsap.utils.shuffle(column);
// Push the randomized column again into the ultimate ordered sequence
ordered.push(...shuffledColumn);
}
// 'ordered' is now: [Column 0 (random), Column 1 (random), ...]
}
The ultimate ordered array finally ends up with the next construction: “column 0 randomized group, column 1 randomized group, column 2 randomized group, …” As a result of GSAP’s stagger is utilized on this array order, it creates a two-layered habits:
Macro view (total): progresses like a wave from left to proper, following the column order
Micro view (element): inside every column, panels seem in a vertically randomized order
This management makes it potential to create a posh, natural rhythm by which the animation strikes from left to proper as a complete, whereas the panels inside every column seem unpredictably from prime to backside.
3. Strong hole prevention
As within the different demos, shape-rendering="crispEdges" can also be used right here. In a grid impact the place many rectangles are drawn quickly and irregularly, stopping 1px gaps brought on by anti-aliasing is crucial for sustaining the clear visible high quality of the design.
Wrapping up
The mix of SVG masks and GSAP (ScrollTrigger) elevates scene transitions on an internet site from a easy operate right into a compelling type of visible course that pulls customers in.
Utilizing the code created right here as a basis, strive customizing the variety of divisions, the order of look, and the easing to create your individual unique transition results.


