Applying Ken Burns Pan Effects with CSS Transforms
Ken Burns pan effects create slow, cinematic horizontal movement using CSS transforms.
Pan Left Effect
Pan from right to left (landscape moves left, camera pans right):
@keyframes panLeft {
from { transform: translateX(0%); }
to { transform: translateX(-10%); }
}
.pan-left {
animation: panLeft 10s ease-in-out forwards;
}
Pan Right Effect
Pan from left to right (landscape moves right, camera pans left):
@keyframes panRight {
from { transform: translateX(-10%); }
to { transform: translateX(0%); }
}
.pan-right {
animation: panRight 10s ease-in-out forwards;
}
Frame-Based Calculation (Remotion)
Calculate translation based on current frame:
function calculatePanTranslation(
frame: number,
duration: number,
direction: 'left' | 'right',
intensity: number = 0.5
): number {
const progress = frame / duration;
const maxOffset = intensity * 10; // 10% at intensity 1.0
if (direction === 'left') {
return -progress * maxOffset; // Negative = pan left
} else {
return -maxOffset + (progress * maxOffset); // Start negative, move to 0
}
}
React Component
interface PanAnimationProps {
frame: number;
duration: number;
direction: 'left' | 'right';
intensity?: number; // 0-1, default 0.5
}
export const PanAnimation: React.FC<PanAnimationProps> = ({
frame,
duration,
direction,
intensity = 0.5,
children
}) => {
const translateX = calculatePanTranslation(frame, duration, direction, intensity);
return (
<div
style={{
transform: `translateX(${translateX}%)`,
width: '110%', // Oversized to prevent edge visibility
height: '100%',
}}
>
{children}
</div>
);
};
Combined Zoom + Pan
For more dynamic effects, combine zoom and pan:
function calculateKenBurnsTransform(
frame: number,
duration: number,
options: {
zoom?: { direction: 'in' | 'out'; intensity: number };
pan?: { direction: 'left' | 'right'; intensity: number };
}
): { scale: number; translateX: number } {
const scale = options.zoom
? calculateZoomScale(frame, duration, options.zoom.direction, options.zoom.intensity)
: 1;
const translateX = options.pan
? calculatePanTranslation(frame, duration, options.pan.direction, options.pan.intensity)
: 0;
return { scale, translateX };
}
Usage Example
export const BackgroundAnimation: React.FC = () => {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
const { scale, translateX } = calculateKenBurnsTransform(frame, durationInFrames, {
zoom: { direction: 'in', intensity: 0.5 },
pan: { direction: 'left', intensity: 0.3 },
});
return (
<div
style={{
transform: `scale(${scale}) translateX(${translateX}%)`,
transformOrigin: 'center center',
width: '100%',
height: '100%',
}}
>
<img src="landscape.jpg" alt="" />
</div>
);
};
Direction Guidelines
| Direction | Effect | Use Case |
|---|---|---|
| Pan Left | Camera moves right, scene moves left | Reading right to left content |
| Pan Right | Camera moves left, scene moves right | Natural reading direction |
| Zoom In | Scale 1.0 → 1.2 | Emphasize details, focus |
| Zoom Out | Scale 1.2 → 1.0 | Reveal context, conclusions |
Intensity Guidelines
| Intensity | Translation | Use Case |
|---|---|---|
| 0.2 | 2% | Subtle movement |
| 0.5 | 5% | Standard pan |
| 0.8 | 8% | Noticeable movement |
| 1.0 | 10% | Maximum pan (may show edges) |
Important Notes
- Oversize image (110%+) to prevent edge visibility during pan
- Use
transform-origin: center centerfor consistent pivot point - Combine with zoom for more dynamic effects
- Test with various aspect ratios