reelcn
GitHubBack to landing

Slice slide

Cuts the frame into horizontal slices that slide out alternately left and right, each pushed out by the matching slice of the next scene.

SliceSlide, default
SliceSlide, default thumbnail, 16:9

Installation

npx shadcn add https://www.reelcn.dev/r/slice-slide.json

Files land in src/reelcn/ with the items it builds on. Dependencies: @remotion/transitions, remotion.

Usage

<TransitionSeries>
  <TransitionSeries.Sequence durationInFrames={60}>
    <LookOne />
  </TransitionSeries.Sequence>
  <TransitionSeries.Transition presentation={sliceSlide({ slices: 6 })} timing={linearTiming({ durationInFrames: 30 })} />
  <TransitionSeries.Sequence durationInFrames={60}>
    <LookTwo />
  </TransitionSeries.Sequence>
</TransitionSeries>

Props

PropTypeDescription
slicesnumberNumber of horizontal slices. Defaults to one per 216 design units of height.
direction"left" | "right"Where the top slice leaves to. The slices below alternate.

Built from

Use

  • Stylised scene changes in promos, music and fashion edits
  • Swapping between two layouts with a strong horizontal rhythm

Avoid

  • Scenes that play audio or video, since both render once per slice. Use brand-sweep

Guides

Components take their colors from a theme, their timing from motion props and their layout from the canvas.

Source

registry/items/slice-slide.tsx
/**
 * @title Slice Slide
 * @category transitions
 * @description Cuts the frame into horizontal slices that slide out alternately left and right, each pushed out by the matching slice of the next scene.
 * @duration 30
 * @use Stylised scene changes in promos, music and fashion edits
 * @use Swapping between two layouts with a strong horizontal rhythm
 * @avoid Scenes that play audio or video, since both render once per slice. Use `brand-sweep`
 * @tags slices, strips, push, stagger
 * @example
 * <TransitionSeries>
 *   <TransitionSeries.Sequence durationInFrames={60}>
 *     <LookOne />
 *   </TransitionSeries.Sequence>
 *   <TransitionSeries.Transition presentation={sliceSlide({ slices: 6 })} timing={linearTiming({ durationInFrames: 30 })} />
 *   <TransitionSeries.Sequence durationInFrames={60}>
 *     <LookTwo />
 *   </TransitionSeries.Sequence>
 * </TransitionSeries>
 */
import type { TransitionPresentation, TransitionPresentationComponentProps } from "@remotion/transitions";
import { AbsoluteFill, interpolate } from "remotion";
import { CLAMP, easings, useViewport } from "./core";

export type SliceSlideProps = {
  /** Number of horizontal slices. Defaults to one per 216 design units of height. */
  slices?: number;
  /** Where the top slice leaves to. The slices below alternate. */
  direction?: "left" | "right";
};

function SliceSlidePresentation({
  children,
  presentationDirection,
  presentationProgress,
  passedProps,
}: TransitionPresentationComponentProps<SliceSlideProps>) {
  const { width, height, u } = useViewport();
  const count = Math.max(2, Math.round(passedProps.slices ?? height / u(216)));
  const first = passedProps.direction === "right" ? 1 : -1;
  const entering = presentationDirection === "entering";
  // Slices start one after another from the top; the last one still gets 70% of the transition to travel.
  const step = 0.3 / (count - 1);
  const span = 1 - step * (count - 1);

  return (
    <AbsoluteFill>
      {Array.from({ length: count }, (_, index) => {
        const top = Math.round((index * height) / count);
        const bottom = Math.round(((index + 1) * height) / count);
        const sign = index % 2 === 0 ? first : -first;
        const travel = interpolate(presentationProgress, [index * step, index * step + span], [0, 1], {
          ...CLAMP,
          easing: easings.gentle,
        });
        // Outgoing slices leave toward `sign`; incoming slices follow them in from the opposite edge.
        const x = sign * (entering ? travel - 1 : travel) * width;
        return (
          <div
            key={index}
            style={{
              position: "absolute",
              left: 0,
              top,
              width,
              height: bottom - top,
              overflow: "hidden",
              translate: `${x}px 0`,
            }}
          >
            <div style={{ position: "absolute", left: 0, top: -top, width, height }}>{children}</div>
          </div>
        );
      })}
    </AbsoluteFill>
  );
}

export function sliceSlide(props: SliceSlideProps = {}): TransitionPresentation<SliceSlideProps> {
  return { component: SliceSlidePresentation, props };
}

On this page