UNPKG

vault66-crt-effect

Version:

A customizable CRT (retro monitor) visual effect for React, Vue, and Web Components

276 lines (216 loc) 21.1 kB
# vault66-crt-effect [**Live playground and documentation →**](https://vault66.dev/) ![npm](https://img.shields.io/npm/v/vault66-crt-effect?style=flat-square) ![npm downloads](https://img.shields.io/npm/dw/vault66-crt-effect?style=flat-square) ![npm total downloads](https://img.shields.io/npm/dt/vault66-crt-effect?style=flat-square) A CRT screen effect you wrap around any content: scanlines, sweep line, glow, flicker, glitch, screen curvature, glare, and static. Ships for **React**, **Vue**, and as a framework-agnostic **Web Component** (so it also works in Angular, plain HTML, Astro, Svelte, or anything else that runs in a browser). --- ![CRT Effect Demo](./crt.gif) ## Installation ```bash npm install vault66-crt-effect pnpm add vault66-crt-effect yarn add vault66-crt-effect bun add vault66-crt-effect ``` ## Use it with your framework Same effect, same props everywhere — only the import and the syntax change. Presets get you a full look in one line; every individual prop is listed in [Props](#props). ### React ```jsx import CRTEffect from "vault66-crt-effect"; import "vault66-crt-effect/style.css"; export default function App() { return ( <CRTEffect preset="fallout"> <div style={{ padding: 20, fontSize: 24 }}>Welcome to the Wasteland!</div> </CRTEffect> ); } ``` ### Vue ```vue <script setup> import CRTEffect from "vault66-crt-effect/vue"; import "vault66-crt-effect/style.css"; </script> <template> <CRTEffect preset="fallout"> <div style="padding: 20px; font-size: 24px">Welcome to the Wasteland!</div> </CRTEffect> </template> ``` ### Web Component Import once to register the `<crt-effect>` element, then use it anywhere — no CSS import needed, the component carries its own styles in a shadow root. Props become attributes: booleans are bare (`fill`), everything else is kebab-case (`sweep-duration="5"`). ```html <script type="module"> import "vault66-crt-effect/element"; </script> <crt-effect preset="fallout"> <div style="padding: 20px; font-size: 24px">Welcome to the Wasteland!</div> </crt-effect> ``` **Angular** uses the same element — just add `CUSTOM_ELEMENTS_SCHEMA` to your component/module so the compiler accepts the custom tag (otherwise you'll hit `NG8001`), and `import "vault66-crt-effect/element"` once: ```ts import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; import "vault66-crt-effect/element"; @Component({ selector: "app-root", standalone: true, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `<crt-effect preset="fallout">…</crt-effect>`, }) export class AppComponent {} ``` ## Props Every prop works the same across React, Vue, and the Web Component. Start from a `preset` and override whatever you want, or set props from scratch. | Prop | Type | Default | Purpose | | --------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `preset` | `"fallout" \| "dos" \| "cyberpunk" \| "arcade" \| "commodore64" \| "apple2" \| "vt100" \| "minimal"` | `undefined` | Apply a complete preset configuration. Individual props can override preset values. See [Presets](#presets) below | | `enabled` | `boolean` | `true` | Enables or disables the entire CRT effect | | `sweepDuration` | `number` | `10` | Duration in seconds for the vertical sweep line animation | | `sweepThickness` | `number` | `10` | Height (thickness in pixels) of the sweep line | | `sweepStyle` | `"classic" \| "soft"` | `"classic"` | Style of the vertical sweep line: `"classic"` is a sharp black line, `"soft"` is a blurred shadow effect | | `scanlineOpacity` | `number` (0 to 1) | `0.2` | Opacity of the scanlines | | `scanlineThickness` | `number` | `2` | Thickness of each scanline in pixels | | `scanlineGap` | `number` | `3` | Gap between scanlines in pixels | | `scanlineColor` | `string` | `"rgba(91, 179, 135, 0.2)"` | Custom hex, RGB, or RGBA scanline color (used only if `theme` is `"custom"`; alpha is controlled by `scanlineOpacity`) | | `enableScanlines` | `boolean` | `true` | Shows or hides the scanlines overlay | | `scanlineOrientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Orientation of the scanlines: `"horizontal"` renders lines from top to bottom, `"vertical"` renders lines from left to right | | `enableSweep` | `boolean` | `true` | Shows or hides the vertical sweep line animation | | `theme` | `"green" \| "amber" \| "blue" \| "custom"` | `"green"` | Predefined scanline color themes (overrides `scanlineColor` unless `custom` is used) | | `enableGlow` | `boolean` | `false` | Enables outer glow effect around the container | | `glowColor` | `string` | `"rgba(0, 255, 128, 0.3)"` | Color of the outer glow | | `enableEdgeGlow` | `boolean` | `false` | Enables inset glow effect around edges | | `edgeGlowColor` | `string` | `"rgba(0, 255, 128, 0.2)"` | Color of the inset edge glow | | `edgeGlowSize` | `number` | `30` | Size in pixels of the inset edge glow (box-shadow inset size) | | `enableFlicker` | `boolean` | `false` | Enables subtle flicker animation for CRT realism | | `flickerIntensity` | `"low" \| "medium" \| "high" \| number` | `0.08` | Flicker depth: preset string or custom number (0-1) where 0=no flicker, 1=max brightness variance | | `flickerSpeed` | `"low" \| "medium" \| "high" \| number` | `0.8` | Flicker animation speed: preset string or custom number in seconds. Lower values = faster flicker | | `enableGlitch` | `boolean` | `false` | Enables shaking/interference glitch animation | | `glitchIntensity` | `"low" \| "medium" \| "high" \| number` | `0.6` | Glitch distance: preset string or custom number (0-1) where 0=no movement, 1=max shake amount | | `glitchSpeed` | `"low" \| "medium" \| "high" \| number` | `0.6` | Glitch animation speed: preset string or custom number in seconds. Lower values = faster glitch | | `enableVignette` | `boolean` | `false` | Enables a subtle edge-darkening vignette overlay | | `vignetteIntensity` | `number` (0 to 1) | `0.4` | Controls vignette darkness at the edges | | `enableCurvature` | `boolean` | `false` | Enables a curved-glass overlay: a soft corner highlight plus darkened, rounded screen edges | | `curvatureIntensity` | `number` (0 to 1) | `0.5` | Strength of the curved-glass edge darkening | | `enableGlare` | `boolean` | `false` | Enables a broad, soft diagonal glass-reflection sheen across the screen | | `glareIntensity` | `number` (0 to 1) | `0.18` | Brightness of the glare/reflection sheen | | `enableNoise` | `boolean` | `false` | Enables an animated RF static/snow overlay generated from SVG fractal noise | | `noiseOpacity` | `number` (0 to 1) | `0.15` | Opacity of the static/snow overlay | | `glitchChromatic` | `boolean` | `false` | Adds animated red/cyan chromatic aberration to the glitch (requires `enableGlitch`) | | `tintText` | `boolean` | `false` | Recolors the wrapped content to a phosphor tint (derived from `theme`) with a soft glow. Children with their own explicit `color` keep it | | `textColor` | `string` | `undefined` | Overrides the phosphor tint color used when `tintText` is on. Any CSS color | | `sweepColor` | `string` | `undefined` | Gives the sweep line a bright, colored glow instead of the default dark refresh bar. Any CSS color | | `fill` | `boolean` | `false` | Stretches the effect to fill its parent's full width and height instead of sizing to its content. See [Full-screen layouts](#full-screen-layouts) | | `children` | content | — | Content rendered inside the effect: React children, the Vue default slot, or slotted DOM in the Web Component | In the Web Component, prop names are kebab-case attributes: `enableGlow``enable-glow`, `sweepDuration``sweep-duration="5"`, and booleans are set by presence (`fill`, `enable-flicker`). ## Presets Eight ready-made monitor looks. Use one as-is, or as a starting point and override individual props. | Preset | Look | | ------------- | --------------------------------------------------------------------------------------------- | | `fallout` | Green Vault-Tec phosphor terminal — medium scanlines, sweep, subtle edge glow, gentle flicker | | `dos` | Amber IBM/Hercules monitor — clean scanlines, no sweep or glow | | `cyberpunk` | Purple/magenta neon — heavy outer + edge glow, fast sweep, flicker, glitch | | `arcade` | Bright saturated green — thick scanlines, fast sweep, strong glow | | `commodore64` | Periwinkle 8-bit home computer — chunky scanlines, subtle flicker | | `apple2` | Green Apple II — tight scanlines, clean (no sweep or glow) | | `vt100` | Pale green DEC terminal — minimal scanlines, no sweep or glow | | `minimal` | Barely-there — subtle scanlines + light vignette only | ```jsx // Use a preset as-is <CRTEffect preset="arcade"> <YourContent /> </CRTEffect> // Override specific preset values <CRTEffect preset="fallout" sweepDuration={5} enableFlicker={false}> <YourContent /> </CRTEffect> ``` ## Customization Start from a preset and change a few values, or build the whole look from individual props. A few common patterns (React syntax; the props are identical in Vue and the Web Component): ```jsx // Custom scanline color and orientation <CRTEffect theme="custom" scanlineColor="#ff6400" scanlineOpacity={0.3} scanlineOrientation="vertical"> <YourContent /> </CRTEffect> // Fine-tune flicker and glitch with numbers <CRTEffect enableFlicker flickerIntensity={0.05} // subtle brightness variance (0-1) flickerSpeed={2} // slow flicker (seconds) enableGlitch glitchIntensity={0.3} // gentle shake (0-1) glitchSpeed={1.5} // medium speed (seconds) > <YourContent /> </CRTEffect> // Or use the "low" / "medium" / "high" shortcuts <CRTEffect enableFlicker flickerIntensity="low" enableGlitch glitchIntensity="high"> <YourContent /> </CRTEffect> ``` ## Full-screen layouts By default the wrapper **sizes to its content** — good for cards, panels, and inline blocks. When you want the effect to cover a whole region or the full viewport, add `fill` so the wrapper stretches to its parent (and the content stretches with it): ```jsx // The parent needs a real height — here, the full viewport <div style={{ width: "100vw", height: "100vh" }}> <CRTEffect preset="fallout" fill> <YourApp /> </CRTEffect> </div> ``` > `fill` fills the **parent**, so the parent must have a resolved height (`100vh`, a fixed `px`, or `100%` inherited down the chain). ## Building your own integration If your framework isn't React or Vue and you'd rather not use the Web Component, the core is exported separately. `computeCrt(options)` takes the same props and hands back everything needed to render the effect onto your own markup — no framework attached. It's exactly what the React and Vue wrappers are built on. It returns four things: ```js const { enabled, wrapper, inner, overlays } = computeCrt({ preset: "fallout" }); ``` - **`enabled`** — `false` when the effect is turned off; render your content untouched. - **`wrapper`** — `{ className, style }` for the outer element. - **`inner`** — `{ className, style }` for the element that holds your content. - **`overlays`** — an array of class names, one per overlay layer. The markup contract is: a **wrapper** element containing an **inner** element (your content goes in there), followed by one empty **overlay** div per entry in `overlays`: ```html <div class="{wrapper.className}" style="{wrapper.style}"> <div class="{inner.className}" style="{inner.style}">…your content…</div> <!-- one sibling div per overlay class --> <div class="crt-vignette"></div> </div> ``` One catch when applying the styles by hand: the `style` bag contains both normal CSS properties (`filter`, `flexDirection`) and CSS custom properties (`--scanline-color-rgb`). Custom properties have to be set with `setProperty`, not by assigning to `el.style`. Here's a complete vanilla-DOM renderer that handles it: ```js import { computeCrt } from "vault66-crt-effect/core"; import "vault66-crt-effect/style.css"; function applyStyle(el, style) { for (const [key, value] of Object.entries(style)) { if (key.startsWith("--")) el.style.setProperty(key, String(value)); else el.style[key] = value; } } // Wrap any element in the CRT effect and return the new root element. function crt(content, options) { const { enabled, wrapper, inner, overlays } = computeCrt(options); if (!enabled) return content; // effect off — hand content back as-is const root = document.createElement("div"); root.className = wrapper.className; applyStyle(root, wrapper.style); const innerEl = document.createElement("div"); innerEl.className = inner.className; if (inner.style) applyStyle(innerEl, inner.style); innerEl.append(content); root.append(innerEl); for (const cls of overlays) { const layer = document.createElement("div"); layer.className = cls; root.append(layer); } return root; } const screen = crt(myContent, { preset: "fallout" }); document.body.append(screen); ``` ## Reduced motion If the system has "Reduce Motion" enabled, animated layers (sweep, flicker, glitch, static) turn themselves off automatically, while layers like scanlines and glow stay visible. Nothing to configure. ## License MIT — see [LICENSE](LICENSE) for details.