483

useParallax

Hook to help create parallax effect

sensorslowtest coverage

Move your mouse over the scene — or tilt your device — to shift the layers and create depth.

parallax-layer-0parallax-layer-1parallax-layer-2parallax-layer-3

Credit of images to Jarom Vogel

import { useParallax } from '@siberiacancode/reactuse';

const LAYERS = [
  {
    id: 'parallax-layer-0',
    src: 'https://jaromvogel.com/images/design/tiger_hunt_parallax/Tiger_hunt_3.png',
    multiplier: 1
  },
  {
    id: 'parallax-layer-1',
    src: 'https://jaromvogel.com/images/design/tiger_hunt_parallax/Tiger_hunt_2.png',
    multiplier: 2
  },
  {
    id: 'parallax-layer-2',
    src: 'https://jaromvogel.com/images/design/tiger_hunt_parallax/Tiger_hunt_1.png',
    multiplier: 3
  },
  {
    id: 'parallax-layer-3',
    src: 'https://jaromvogel.com/images/design/tiger_hunt_parallax/Tiger_hunt_0.png',
    multiplier: 4
  }
];

const Demo = () => {
  const parallax = useParallax<HTMLDivElement>((value) => {
    const card = document.getElementById('parallax-card');
    if (card) {
      card.style.transform = `rotateX(${value.roll * 20}deg) rotateY(${value.tilt * 20}deg)`;
    }

    LAYERS.forEach((layer) => {
      const element = document.getElementById(layer.id);
      if (!element) return;
      element.style.transform = `translateX(${value.tilt * layer.multiplier * 10}px) translateY(${value.roll * layer.multiplier * 10}px)`;
    });
  });

  return (
    <section
      ref={parallax.ref}
      className='flex min-h-96 flex-col items-center justify-center gap-6 p-6'
    >
      <p className='text-muted-foreground max-w-xs text-center text-sm'>
        Move your mouse over the scene — or tilt your device — to shift the layers and create depth.
      </p>

      <div className='perspective-[300px]'>
        <div
          className='border-border flex h-72 w-56 items-center justify-center overflow-hidden rounded-xl border bg-white shadow-lg transition-transform duration-300 ease-out'
          id='parallax-card'
        >
          <div className='relative size-[4em] overflow-hidden text-6xl'>
            {LAYERS.map((layer) => (
              <img
                key={layer.id}
                alt={layer.id}
                className='absolute h-full w-full transition-transform duration-300 ease-out'
                id={layer.id}
                src={layer.src}
              />
            ))}
          </div>
        </div>
      </div>

      <p className='text-muted-foreground text-xs'>
        Credit of images to{' '}
        <a
          className='text-primary hover:underline'
          href='https://codepen.io/jaromvogel'
          rel='noreferrer'
          target='_blank'
        >
          Jarom Vogel
        </a>
      </p>
    </section>
  );
};

export default Demo;

Installation

pnpm add @siberiacancode/reactuse

Usage

const { snapshot, watch } = useParallax(ref, (value) => console.log(value));
// or
const { snapshot, watch } = useParallax(ref, options);
// or
const { ref, snapshot, watch } = useParallax<HTMLDivElement>((value) => console.log(value));
// or
const { ref, snapshot, watch } = useParallax<HTMLDivElement>(options);

Type Declarations

import type { HookTarget } from '@/utils/helpers';

import type { StateRef } from '../useRefState/useRefState';

interface InternalDeviceOrientationValue {
  absolute: boolean;
  alpha: number | null;
  beta: number | null;
  gamma: number | null;
}

interface InternalScreenOrientationValue {
  angle: number;
  orientationType?: OrientationType;
}

export interface UseParallaxValue {
  /** Roll value. Scaled to `-0.5 ~ 0.5` */
  roll: number;
  /** Sensor source, can be `mouse` or `deviceOrientation` */
  source: 'deviceOrientation' | 'mouse';
  /** Tilt value. Scaled to `-0.5 ~ 0.5` */
  tilt: number;
}

export type UseParallaxCallback = (value: UseParallaxValue, event: Event) => void;

export interface UseParallaxOptions {
  /** Callback invoked on parallax updates */
  onChange?: UseParallaxCallback;
  /** Device orientation roll adjust function */
  deviceOrientationRollAdjust?: (value: number) => number;
  /** Device orientation tilt adjust function */
  deviceOrientationTiltAdjust?: (value: number) => number;
  /** Mouse roll adjust function */
  mouseRollAdjust?: (value: number) => number;
  /** Mouse tilt adjust function */
  mouseTiltAdjust?: (value: number) => number;
}

interface UseParallaxReturn {
  snapshot: UseParallaxValue;
  supported: boolean;
  watch: () => UseParallaxValue;
}

export interface UseParallax {
  (target: HookTarget, callback?: UseParallaxCallback): UseParallaxReturn;

  (target: HookTarget, options?: UseParallaxOptions): UseParallaxReturn;

  <Target extends Element>(
    callback?: UseParallaxCallback,
    target?: never
  ): UseParallaxReturn & {
    ref: StateRef<Target>;
  };

  <Target extends Element>(
    options?: UseParallaxOptions,
    target?: never
  ): UseParallaxReturn & {
    ref: StateRef<Target>;
  };
}

API

Parameters

NameTypeDefaultNote
targetHookTarget-The target element for the parallax effect
callback(value: UseParallaxValue, event: Event) => void-The callback invoked on parallax updates

Returns

UseParallaxReturn

Parameters

NameTypeDefaultNote
targetHookTarget-The target element for the parallax effect
optionsUseParallaxOptions-The options for the parallax effect

Returns

UseParallaxReturn

Parameters

NameTypeDefaultNote
callback(value: UseParallaxValue, event: Event) => void-The callback invoked on parallax updates

Returns

UseParallaxReturn & { ref: StateRef<Target> }

Parameters

NameTypeDefaultNote
optionsUseParallaxOptions-The options for the parallax effect

Returns

UseParallaxReturn & { ref: StateRef<Target> }

Contributors

ddebabin

Last updated on