compositing
ساخت کامپوزیشنهای چندلایه با blend mode، افکت شیشهای، split screen و PiP
این Skill برای طراحی و پیادهسازی انیمیشنهای چندلایه و افکتهای compositing در وب است؛ از blend mode و backdrop blur تا crossfade، split screen و picture-in-picture.
با الگوهای آماده، زمانبندی استاندارد و پشتیبانی از reduced motion، میتوانید تجربههای بصری پیچیده را منظم، قابلاستفاده و سازگار با نیازهای دسترسپذیری بسازید.
چه چیزی میگیرید
- الگوهای glass morphism، scrim و backdrop blur متحرک
- transition برای blend mode، crossfade و افکتهای رنگی لایهها
- انیمیشن stagger برای لایههای رویهمچیده و split screen افقی یا عمودی
- ساخت PiP قابلجابجایی با قابلیت minimize، expand و snap به گوشهها
- راهکارهای reduced motion برای غیرفعالکردن حرکتهای غیرضروری
برای چه کاری خوب است
- طراحان و توسعهدهندگان UI با رابطهای چندلایه و تعاملی
- ساخت داشبورد، مدیاپلیر، تجربههای storytelling و صفحات معرفی محصول
- پروژههایی که به glass effect، overlay، split view یا PiP نیاز دارند
برای چه کاری نیست
- انیمیشنهای ساده تکعنصری که به compositing چندلایه نیاز ندارند
- تدوین و رندر ویدیو در سطح نرمافزارهای تخصصی مانند After Effects
- پروژههایی که مرورگر هدف از backdrop-filter یا blend mode پشتیبانی نمیکند
دربارهٔ این اسکیل
Compositing
Layer-based animation and visual effects compositing.
Standards Reference
| Standard | Documentation |
|---|---|
| W3C DTCG | designtokens.org |
| CSS Blend Modes | developer.mozilla.org/CSS/mix-blend-mode |
| CSS Backdrop Filter | developer.mozilla.org/CSS/backdrop-filter |
Quick Reference
Load from: tokens/compositing.tokens.json
Layer Timing
| Token | Value | Use |
|---|---|---|
stagger.tight | 50ms | Rapid layer sequence |
stagger.normal | 100ms | Standard stagger |
crossfade.normal | 400ms | Layer crossfade |
Backdrop Blur
| Token | Value | Use |
|---|---|---|
subtle | 4px | Light glass effect |
medium | 12px | Standard glass |
heavy | 24px | Frosted glass |
Selection Guide
What compositing effect?
│
├── BLEND MODES → Color mixing
│ ├── Multiply → darken
│ ├── Screen → lighten
│ ├── Overlay → contrast
│ └── Transition between modes
│
├── GLASS/BLUR → Frosted effects
│ ├── Backdrop blur → glass morphism
│ ├── Scrim → darkened overlay
│ └── Animated blur changes
│
├── LAYERS → Stacked elements
│ ├── Z-index animation
│ ├── Staggered reveals
│ └── Crossfade transitions
│
├── SPLIT SCREEN → Multiple views
│ ├── Horizontal split
│ ├── Vertical split
│ └── Animated splits
│
└── PICTURE-IN-PICTURE → Inset content
├── Corner PiP
├── Draggable PiP
└── Expand/collapse
Patterns
Glass Morphism
Frosted glass effect with animation.
CSS:
.glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(12px); /* {motion.compositing.backdrop-blur.medium} */
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: backdrop-filter 300ms ease-out;
}
.glass.focused {
backdrop-filter: blur(24px); /* {motion.compositing.backdrop-blur.heavy} */
}
Animated blur:
function animateBlur(element, fromBlur, toBlur, duration = 300) {
const startTime = performance.now();
function update(currentTime) {
const progress = Math.min((currentTime - startTime) / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
const currentBlur = fromBlur + (toBlur - fromBlur) * eased;
element.style.backdropFilter = `blur(${currentBlur}px)`;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
Scrim Overlay
Animated darkening overlay.
CSS:
.scrim {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0);
pointer-events: none;
transition: background 300ms ease-out;
}
.scrim.active {
background: rgba(0, 0, 0, 0.5);
/* {motion.compositing.opacity-layers.scrim-medium} */
pointer-events: auto;
}
/* With blur */
.scrim.blur.active {
backdrop-filter: blur(4px);
}
Blend Mode Transition
Animated blend mode changes.
JavaScript:
class BlendTransition {
constructor(element) {
this.element = element;
this.overlay = this.createOverlay();
}
createOverlay() {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: absolute;
inset: 0;
opacity: 0;
transition: opacity 300ms;
`;
this.element.style.position = 'relative';
this.element.appendChild(overlay);
return overlay;
}
transitionTo(blendMode, color, duration = 300) {
// Fade in new blend
this.overlay.style.background = color;
this.overlay.style.mixBlendMode = blendMode;
this.overlay.animate([
{ opacity: 0 },
{ opacity: 1 }
], {
duration,
fill: 'forwards',
easing: 'ease-out'
});
}
remove(duration = 300) {
this.overlay.animate([
{ opacity: 1 },
{ opacity: 0 }
], {
duration,
fill: 'forwards',
easing: 'ease-in'
}).onfinish = () => {
this.overlay.style.opacity = 0;
};
}
}
Layer Stagger
Sequential layer animations.
CSS:
.layer-stack .layer {
opacity: 0;
transform: translateY(20px);
}
.layer-stack.animate .layer {
animation: layer-in 400ms ease-out forwards;
}
.layer-stack.animate .layer:nth-child(1) { animation-delay: 0ms; }
.layer-stack.animate .layer:nth-child(2) { animation-delay: 100ms; }
/* {motion.compositing.layer-timing.stagger.normal} */
.layer-stack.animate .layer:nth-child(3) { animation-delay: 200ms; }
.layer-stack.animate .layer:nth-child(4) { animation-delay: 300ms; }
@keyframes layer-in {
to {
opacity: 1;
transform: translateY(0);
}
}
Framer Motion:
const containerVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.1 // {motion.compositing.layer-timing.stagger.normal}
}
}
};
const layerVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
<motion.div variants={containerVariants} initial="hidden" animate="visible">
{layers.map((layer, i) => (
<motion.div key={i} variants={layerVariants}>
{layer}
</motion.div>
))}
</motion.div>
Crossfade Layers
Smooth layer crossfade transition.
CSS:
.crossfade-container {
position: relative;
}
.crossfade-layer {
position: absolute;
inset: 0;
transition: opacity 400ms ease-out;
/* {motion.compositing.layer-timing.crossfade.normal} */
}
.crossfade-layer.active {
opacity: 1;
z-index: 1;
}
.crossfade-layer:not(.active) {
opacity: 0;
z-index: 0;
}
JavaScript:
function crossfade(fromLayer, toLayer, duration = 400) {
// Prepare toLayer
toLayer.style.opacity = '0';
toLayer.style.zIndex = '1';
// Animate both simultaneously
fromLayer.animate([
{ opacity: 1 },
{ opacity: 0 }
], { duration, fill: 'forwards' });
toLayer.animate([
{ opacity: 0 },
{ opacity: 1 }
], { duration, fill: 'forwards' });
}
Split Screen
Animated split screen effect.
CSS:
.split-container {
display: flex;
height: 100vh;
overflow: hidden;
}
.split-left,
.split-right {
flex: 1;
transition: flex 800ms cubic-bezier(0.4, 0, 0.2, 1);
/* {motion.compositing.split-screen.duration.normal} */
}
.split-container.focus-left .split-left {
flex: 2;
}
.split-container.focus-left .split-right {
flex: 1;
}
/* With gap */
.split-container.with-gap {
gap: 8px; /* {motion.compositing.split-screen.gap.normal} */
}
Vertical split:
.split-vertical {
flex-direction: column;
}
.split-top,
.split-bottom {
flex: 1;
transition: flex 800ms cubic-bezier(0.4, 0, 0.2, 1);
}
Picture-in-Picture
Floating PiP element.
CSS:
.pip-container {
position: fixed;
bottom: 24px;
right: 24px;
width: 320px;
aspect-ratio: 16/9;
border-radius: 12px; /* {motion.compositing.picture-in-picture.corner-radius} */
overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
/* {motion.compositing.picture-in-picture.transition} */
z-index: 1000;
}
.pip-container.minimized {
width: 160px;
opacity: 0.8;
}
.pip-container.expanded {
width: 100%;
height: 100%;
bottom: 0;
right: 0;
border-radius: 0;
}
Draggable PiP:
class DraggablePiP {
constructor(element) {
this.element = element;
this.isDragging = false;
this.currentPos = { x: 0, y: 0 };
this.bind();
}
bind() {
this.element.addEventListener('mousedown', (e) => this.startDrag(e));
document.addEventListener('mousemove', (e) => this.drag(e));
document.addEventListener('mouseup', () => this.endDrag());
}
startDrag(e) {
this.isDragging = true;
this.element.style.transition = 'none';
this.startPos = {
x: e.clientX - this.currentPos.x,
y: e.clientY - this.currentPos.y
};
}
drag(e) {
if (!this.isDragging) return;
this.currentPos = {
x: e.clientX - this.startPos.x,
y: e.clientY - this.startPos.y
};
this.element.style.transform = `translate(${this.currentPos.x}px, ${this.currentPos.y}px)`;
}
endDrag() {
this.isDragging = false;
this.element.style.transition = 'transform 300ms ease-out';
this.snapToCorner();
}
snapToCorner() {
const rect = this.element.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// Determine closest corner
const snapX = centerX < window.innerWidth / 2 ? 24 : window.innerWidth - rect.width - 24;
const snapY = centerY < window.innerHeight / 2 ? 24 : window.innerHeight - rect.height - 24;
this.element.style.left = `${snapX}px`;
this.element.style.top = `${snapY}px`;
this.element.style.right = 'auto';
this.element.style.bottom = 'auto';
this.element.style.transform = 'none';
this.currentPos = { x: 0, y: 0 };
}
}
Color Overlay Animation
Animated color grading overlay.
CSS:
.color-overlay {
position: absolute;
inset: 0;
pointer-events: none;
mix-blend-mode: overlay;
opacity: 0;
transition: opacity 500ms ease-out, background 500ms ease-out;
/* {motion.compositing.color-overlay.fade-speed.normal} */
}
.color-overlay.warm {
background: rgba(255, 200, 150, 0.3);
opacity: 1;
}
.color-overlay.cool {
background: rgba(150, 200, 255, 0.3);
opacity: 1;
}
.color-overlay.dramatic {
background: linear-gradient(
to bottom,
rgba(0, 0, 50, 0.3),
rgba(50, 0, 50, 0.3)
);
opacity: 1;
}
Layer Effects Animation
Filter transitions.
CSS:
.layer {
transition: filter 500ms ease-out;
}
.layer.desaturated {
filter: saturate(0); /* {motion.compositing.layer-effects.saturation.desaturate} */
}
.layer.muted {
filter: saturate(0.5); /* {motion.compositing.layer-effects.saturation.muted} */
}
.layer.vibrant {
filter: saturate(1.3) contrast(1.1);
/* {motion.compositing.layer-effects.saturation.vibrant} */
}
.layer.darkened {
filter: brightness(0.7);
/* {motion.compositing.layer-effects.brightness.dark} */
}
Reduced Motion
For prefers-reduced-motion: reduce:
| Full Motion | Reduced Alternative |
|---|---|
| Layer stagger | Instant reveal |
| Crossfade | Instant swap |
| Split animation | Instant split |
| PiP transition | Instant position |
| Blur animation | Static blur |
Implementation:
@media (prefers-reduced-motion: reduce) {
.layer-stack .layer,
.crossfade-layer,
.split-left,
.split-right,
.pip-container {
transition: none;
}
.layer-stack.animate .layer {
animation: none;
opacity: 1;
transform: none;
}
}
Cross-References
- See
opacity/for fade patterns - See
blur/for blur effects - See
masks/for layer masking - See
transitions/for page transitions
References
references/blend-modes.md— Blend mode reference guide