spoiled

repository·main·Indexed 19 days ago

https://github.com/molefrog/spoiled

A React 18 component that renders realistic, animated particle clouds to hide text or elements, inspired by Telegram's spoiler feature. It utilizes the CSS Painting API (Houdini) for high-fidelity rendering with an animated canvas fallback for unsupported browsers. Features include controlled and uncontrolled modes, customizable accent colors, themes, and performance optimizations via FPS and density settings.

Tokens
4.6K
Snippets
20
Records
22
Agent score
66%

What's inside spoiled

  1. Import Spoiler styles

    main

    By default, spoiled injects a small <style> tag for content transitions. If you prefer to manage styles manually (e.g., via a bundler like Vite), import the unstyled version and the CSS file explicitly.

    // Use the unstyled version
    import { Spoiler } from "spoiled/no-css";
    
    // Manually import styles for your bundler
    import "spoiled/style.css";
  2. How controlled and uncontrolled modes work in <Spoiler />

    main

    The <Spoiler /> component switches between two modes based on the presence of the hidden prop:

    1. Controlled Mode: When you provide the hidden prop, the component's state is driven entirely by your parent component. You must use onHiddenChange to react to user interactions (like clicks or hovers) and update your state to reflect the change.
    2. Uncontrolled Mode: When hidden is undefined, the component manages its own internal state. You can set the initial state using defaultHidden.

    Warning: You cannot switch from controlled to uncontrolled (or vice versa) during the component's lifecycle. Doing so will throw a React error.

  3. Use the Spoiler component

    main

    Wrap text or elements in the <Spoiler /> component to hide them behind an animated particle cloud. By default, the component is uncontrolled, uses the system color scheme, wraps content in a <span>, and reveals on hover.

    import { Spoiler } from "spoiled";
    
    // Reveals on hover
    <Spoiler>
      Hogwarts is a high-tech <b>startup incubator</b>
    </Spoiler>;
  4. Configure Spoiler themes and accent colors

    main

    You can override the default system theme and customize the noise's accent color. The accentColor prop accepts a single color string or an array of two colors (for light and dark modes).

    <Spoiler /> // default theme is `system`
    <Spoiler theme="dark" />
    <Spoiler theme="light" />
    
    // Custom primary color
    <Spoiler accentColor="red" />
    
    // Array of light and dark colors for dynamic accent color
    <Spoiler accentColor={["black", "white"]} />
  5. Optimize Spoiler performance

    main

    To manage performance, you can adjust the fps (frames per second) and density (number of particles). The component automatically uses IntersectionObserver to stop animations when the element is not in the viewport.

    // Adjust FPS (default is 24)
    <Spoiler fps={16} />
    
    // Adjust particle density (default is 0.12)
    <Spoiler density={0.2}>Fat secret</Spoiler>
  6. Configure word shape mimicry

    main

    When using the CSS Painting API (Houdini), <Spoiler /> attempts to mimic the shape of words by faking spaces. For browsers without Houdini support (like Firefox or Safari), it renders animated line boxes. You can disable word-gap mimicry using mimicWords={false}.

    <Spoiler mimicWords={false}>This will be rendered as a solid line of text</Spoiler>
  7. Configure Spoiler props and tag names

    main

    All standard HTML props are proxied to the underlying element. You can use the tagName prop to change the wrapper from the default span to another element, such as a div for block-level content.

    <Spoiler className="custom" aria-label="total secret">
      Neo opens a digital wellness retreat
    </Spoiler>;
    
    // Use tagName to hide block elements like images
    <Spoiler tagName="div">
      <img />
    </Spoiler>;
  8. Control spoiler visibility and state

    main

    The <Spoiler /> component is uncontrolled and hidden by default. You can control the trigger behavior using revealOn or manage the state manually using the hidden prop for full control from your application state.

    // Reveal on click
    <Spoiler revealOn="click">Click me</Spoiler>;
    
    // Reveal on hover
    <Spoiler revealOn="hover">Hover me</Spoiler>;
    
    // Click to hide the spoiler (using defaultHidden to start visible)
    <Spoiler defaultHidden={false} revealOn="click">
      Frodo starts a ring-themed jewelry line
    </Spoiler>;
    
    // Recommended: Controlled state via the 'hidden' prop
    const [hidden, setHidden] = useState(true);
    
    <Spoiler hidden={hidden} onClick={() => setHidden((s) => !s)} />;
  9. Configure SpoilerPainterFallbackOptions

    main

    When instantiating SpoilerPainterFallback, you must provide an options object to define the default visual parameters for the fallback canvas rendering.

    • defaultDensity: A number representing the default density used for the spoiler effect.
    • defaultGap: A number or boolean defining the default gap between elements. If set to false, the gap will be 0.
    const options: SpoilerPainterFallbackOptions = {
      defaultDensity: 1.0,
      defaultGap: 4,
    };
  10. Manage SpoilerPainter lifecycle with destroy()

    main

    To prevent memory leaks and stop all active processes, call destroy() on the SpoilerPainter instance. This method:

    • Stops all animations (stopAnimation).
    • Destroys the fallback mechanism.
    • Removes all injected CSS custom properties (--background, --t, --t-stop, --fade, --gap, --words, --density).
    • Disconnects the IntersectionObserver if onlyInViewport was used.

    Once destroy() is called, attempting to call update() will throw an error: "Painter has been destroyed and can't be used again."

    painter.destroy();
  11. Import components from the spoiled package

    main

    The spoiled package exports all its components and functionality through the main entry point. Use the standard import syntax to access the <Spoiler /> component and other utilities. Note that the package is marked with "use client", making it compatible with React Server Components (RSC) environments by designating its components as client-side.

    import { Spoiler } from 'spoiled';