npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@scaleflex/image-crop

v2.0.3

Published

Interactive image crop tool with rotation, flip, zoom and shape selection — Scaleflex Web Component

Downloads

281

Readme

Table of Contents

Overview

@scaleflex/image-crop ships <sfx-crop>, a Lit-based custom element that renders a canvas-backed crop editor with rotation, fine tilt (±45°), horizontal/vertical flip, zoom, pan, and a configurable shape palette (free, square, circle, rounded-rect, plus arbitrary W:H ratio strings). The same engine is exposed three ways:

  • a ready-to-mount custom element (<sfx-crop>);
  • a React component (<SfxCrop>) plus hooks (useSfxCrop, useSfxCropController);
  • a headless createCropController({ canvas, host, config }) factory that drives a consumer-owned <canvas> with zero built-in UI.

Features

  • Two display variants: classic (movable / resizable crop frame over the photo) and fixed (the editor box is the crop frame — the photo is cover-fit and panned underneath, e.g. avatar / phone-style cropping). See Variants.
  • Rotation in 90° increments and fine tilt slider (-45°…+45°), horizontal flip, pinch / wheel / button zoom, keyboard shortcuts.
  • Built-in shape presets (free, square, circle, rounded-rect, 16:9, 4:3, 3:2, 5:4, 2:1, 9:16, 3:4, 2:3, 4:5, 1:2) plus on-the-fly "W:H" ratios.
  • Optional bleed-margin guides for print workflows.
  • Themeable via a single theme="light|dark" attribute or fine-grained --sfx-cr-* CSS custom properties (~50 tokens).
  • Per-icon SVG overrides via the icons property.
  • Export to HTMLCanvasElement, Blob, data URL, or a serialisable TransformParams object suitable for server-side processing.
  • Three packaging entry points so consumers pay for only what they use.

Requirements

Modern evergreen browsers with Canvas 2D, Pointer Events, ResizeObserver, CSS container queries, and Custom Elements v1. React 18+ for the React entry. Node 18+ recommended for tooling.

Installation

npm / yarn / pnpm

npm install @scaleflex/image-crop
# or
yarn add @scaleflex/image-crop
# or
pnpm add @scaleflex/image-crop

CDN

The official Scaleflex CDN serves a single self-contained bundle that registers <sfx-crop> on load (plain <script>, no build step):

<script src="https://cdn.scaleflex.com/image-crop/2.0.3/image-crop.min.js"></script>

Or load the ESM build straight from npm via jsDelivr's auto-bundling +esm endpoint (resolves lit for the browser):

<script type="module"
        src="https://cdn.jsdelivr.net/npm/@scaleflex/image-crop/dist/define.js/+esm"></script>

Package exports

| Specifier | Purpose | |---|---| | @scaleflex/image-crop | Side-effect-free entry. Exports SfxCropElement, createCropController, mergeConfig, DEFAULT_CONFIG, and all public types. | | @scaleflex/image-crop/define | Side-effectful — registers the <sfx-crop> custom element. Import once at bootstrap. | | @scaleflex/image-crop/react | React component <SfxCrop>, useSfxCrop / useSfxCropController hooks, plus re-exports of createCropController, mergeConfig, DEFAULT_CONFIG, and the public types. |

Quick Start

Vanilla JS / Web Component

<script type="module">
  import '@scaleflex/image-crop/define';
</script>

<sfx-crop
  src="https://cdn.example.com/photo.jpg"
  crop-shape="16:9"
  theme="light"
  show-bleed-margin
></sfx-crop>

<script type="module">
  const crop = document.querySelector('sfx-crop');
  crop.addEventListener('sfx-crop-ready', () => console.log('ready'));
  crop.addEventListener('sfx-crop-save', (e) => {
    const { blob, dataURL, params } = e.detail;
    // upload `blob` or POST `params` to your backend
  });
</script>

React

import { SfxCrop, type SfxCropElement } from '@scaleflex/image-crop/react';
import { useRef } from 'react';

export function Editor() {
  const ref = useRef<SfxCropElement>(null);
  return (
    <SfxCrop
      ref={ref}
      src="https://cdn.example.com/photo.jpg"
      cropShape="square"
      theme="dark"
      onReady={({ element }) => console.log('ready', element)}
      onSave={({ blob, params }) => upload(blob, params)}
    />
  );
}

CDN

<!-- Self-contained bundle from the Scaleflex CDN -->
<script src="https://cdn.scaleflex.com/image-crop/2.0.3/image-crop.min.js"></script>
<sfx-crop src="https://cdn.example.com/photo.jpg" crop-shape="square"></sfx-crop>

Configuration

All options below are exposed as both HTML attributes (kebab-case) and DOM properties (camelCase) on <sfx-crop>. Object/array options should be set as DOM properties; primitives can be set either way.

Attributes / Properties

Image & shape

| Attribute / Property | Type | Default | Description | |---|---|---|---| | src | string | '' | Image URL to load. Setting after mount triggers a re-load. | | variant | 'classic' \| 'fixed' | 'classic' | Display mode. classic = movable/resizable crop frame over the photo. fixed = the editor box itself is the crop frame (sized to crop-shape), photo cover-fit and panned underneath, toolbar overlaid. Switchable at runtime. See Variants. | | crop-shape / cropShape | CropShapeName | '16:9' | Built-in preset or any "W:H" ratio string. | | available-shapes / availableShapes | CropShapeName[] \| string | ['free','square','16:9','4:3','3:2','5:4','2:1','9:16','3:4','2:3','4:5','1:2'] | Restricts the shape palette in the toolbar. JSON-stringified array works as an attribute. circle and rounded-rect are valid built-ins but omitted from the default — pass them explicitly to enable. | | initial-crop / initialCrop | CropRect \| string \| null | null | Starting crop rect in normalised [0,1] image coords. | | initial-rotation / initialRotation | number | 0 | Starting fine rotation, degrees. | | initial-scale / initialScale | number | 1 | Starting zoom level. |

Constraints

| Attribute / Property | Type | Default | Description | |---|---|---|---| | min-scale / minScale | number | 0.5 | Minimum zoom level. | | max-scale / maxScale | number | 5 | Maximum zoom level. | | min-crop-size / minCropSize | number | 20 | Minimum crop edge in canvas pixels. | | handle-size / handleSize | number | 12 | Resize-handle radius. | | border-radius / borderRadius | number | 20 | Corner radius for the rounded-rect shape. |

Theme & colours

| Attribute / Property | Type | Default | Description | |---|---|---|---| | theme | 'light' \| 'dark' | 'light' | Switches the bundled palette. Override individual tokens via CSS variables. | | handle-color / handleColor | string | '#ffffff' | Frame handle fill. | | overlay-color / overlayColor | string | 'rgba(0,0,0,0.55)' | Mask covering the area outside the crop. | | bleed-margin-color / bleedMarginColor | string | 'rgba(255,0,0,0.5)' | Print bleed guide colour. | | bleed-margin-size / bleedMarginSize | number | 10 | Bleed inset in pixels. | | show-bleed-margin / showBleedMargin | boolean | false | Toggles the print bleed guides. |

Print bleed guides. In print, artwork is printed slightly larger than the final size and trimmed at the cut line; the bleed is the strip near the edge that gets cut off. show-bleed-margin draws a dashed safe-area rectangle inset bleed-margin-size pixels from every edge of the crop, so important content (faces, text, logos) can be kept clear of the trim. It is a visual guide only — it is not baked into the output: toCanvas(), toBlob(), toDataURL(), and the sfx-crop-save payload never contain the dashed line and the crop is not inset by it.

UI toggles

| Attribute / Property | Type | Default | Description | |---|---|---|---| | show-toolbar / showToolbar | boolean | true | Renders the built-in toolbar. | | toolbar-position / toolbarPosition | 'top' \| 'bottom' | 'top' | Toolbar placement. | | show-rotate-button / showRotateButton | boolean | true | 90° rotate-left button. | | show-flip-button / showFlipButton | boolean | true | Horizontal flip button. | | show-rotate-slider / showRotateSlider | boolean | true | Fine tilt slider (±45°). | | show-zoom-slider / showZoomSlider | boolean | true | Zoom slider. | | show-shape-selector / showShapeSelector | boolean | true | Shape dropdown. | | show-grid / showGrid | boolean \| 'interaction' | 'interaction' | Rule-of-thirds overlay; 'interaction' shows it only while dragging. |

Output

| Attribute / Property | Type | Default | Description | |---|---|---|---| | output-type / outputType | string | 'image/png' | MIME type for toBlob / toDataURL. | | output-quality / outputQuality | number | 0.92 | Quality 0–1 for lossy types. | | max-output-width / maxOutputWidth | number | 0 | 0 = original. | | max-output-height / maxOutputHeight| number | 0 | 0 = original. |

Behaviour

| Attribute / Property | Type | Default | Description | |---|---|---|---| | keyboard | boolean | true | Arrow-key nudge / shift-zoom. | | pinch-zoom / pinchZoom | boolean | true | Two-finger touch zoom. | | wheel-zoom / wheelZoom | boolean | true | Mouse-wheel zoom. | | enable-animations / enableAnimations | boolean | true | Spring/lerp transitions. | | animation-speed / animationSpeed | number | 1.0 | Multiplier on the default spring. | | icons (property only) | CropIconOverrides | {} | SVG-string slot overrides — see CropIconOverrides in src/core/types.ts. |

Variants

The variant attribute switches how the crop is presented. Both share the same engine, tools, events, and export.

classic (default)

The photo fills the editor at its own aspect ratio and a movable / resizable crop frame floats over it (resize handles + a move-handle), with the area outside the frame dimmed by overlay-color. Drag inside the frame to pan the photo, drag the handles to resize.

fixed

The editor box itself is the crop frame, sized to the crop-shape aspect and centred (portrait, landscape, square, circle, rounded-rect — anything). The photo is cover-fit and panned/zoomed/rotated underneath; there are no resize handles, and the toolbar is overlaid on the frame. This is the avatar- / phone-style "fixed window, moving photo" pattern.

<sfx-crop src="/photo.jpg" variant="fixed" crop-shape="1:1"></sfx-crop>

Cover guarantee

In both variants the photo is constrained to always fully cover the crop frame — zoom and pan are clamped (and the minimum zoom is raised) so the exported image never contains transparent gaps. The one exception is a 90°/270° turn in classic, which intentionally letterboxes the rotated photo to fit the frame.

Built-in "Done" button

The toolbar renders a primary Done button pinned to the right edge. It calls save() — building blob + dataURL + params and dispatching sfx-crop-save — so a host app can commit the crop without wiring its own button. Handle sfx-crop-save to upload / persist / close. (Hide the whole toolbar with show-toolbar="false" if you prefer to drive everything imperatively.)

Public Methods

All methods live on the <sfx-crop> element instance. They throw if invoked before sfx-crop-ready fires.

| Method | Returns | Description | |---|---|---| | loadImage(src) | Promise<void> | Load (or re-load) an image URL. | | getTransformState() | TransformState | Snapshot of rotation, flip, scale, pan, crop. | | getCropRect() | CropRect | Current crop in normalised [0,1] coords. | | setCropRect(rect) | void | Programmatic crop update. | | setCropShape(shape) | void | Built-in preset or "W:H" ratio. | | rotateLeft() | void | 90° counter-clockwise. | | flipHorizontal() | void | Mirror around vertical axis. | | setRotation(deg) | void | Fine tilt -45…+45. | | setScale(scale) | void | Zoom level (clamped to min/max-scale). | | reset() | void | Restore initial state. | | toCanvas() | HTMLCanvasElement | Render the current crop into a fresh canvas. | | toBlob(type?, quality?) | Promise<Blob> | Like HTMLCanvasElement.toBlob for the cropped output. | | toDataURL(type?, quality?) | string | Like HTMLCanvasElement.toDataURL. | | toTransformParams() | TransformParams | Serialisable description of the transform — pass to a server-side resizer. | | save(type?, quality?) | Promise<void> | Convenience: builds blob + dataURL + params and dispatches sfx-crop-save. | | cancel() | void | Dispatches sfx-crop-cancel. |

Events

All events bubble and cross shadow boundaries (bubbles: true, composed: true).

| Event | detail | Fires on | |---|---|---| | sfx-crop-ready | { element: SfxCropElement } | Controller initialised. | | sfx-crop-image-load | { image: HTMLImageElement } | Image decoded and rendered. | | sfx-crop-change | TransformState | Any transform mutation. | | sfx-crop-crop-change | CropRect | Crop rect changed. | | sfx-crop-save | { blob, dataURL, params } | .save() resolved. | | sfx-crop-cancel | undefined | .cancel() invoked. | | sfx-crop-error | { error: Error } | Image-load or export error. |

React API

<SfxCrop> component

forwardRef component that mirrors the element's attributes as camelCase props and bridges every sfx-crop-* event into a matching on* callback.

import { SfxCrop } from '@scaleflex/image-crop/react';

<SfxCrop
  src="..."
  cropShape="circle"
  theme="dark"
  showBleedMargin
  availableShapes={['free', 'square', 'circle', '16:9']}
  onReady={({ element }) => {}}
  onImageLoad={({ image }) => {}}
  onChange={(state) => {}}
  onCropChange={(crop) => {}}
  onSave={({ blob, dataURL, params }) => {}}
  onCancel={() => {}}
  onError={({ error }) => {}}
/>

The ref resolves to the underlying SfxCropElement, so every imperative method above is callable directly.

useSfxCrop() hook

For consumers who prefer to render <sfx-crop> themselves and pull stable callables off a hook:

import { useSfxCrop } from '@scaleflex/image-crop/react';

const { ref, ready, save, reset, toBlob, getTransformState } = useSfxCrop();

return <sfx-crop ref={ref} src="..." />;

ready flips to true after sfx-crop-ready. All callables are no-ops before then.

useSfxCropController() hook (headless)

Drives the same controller against a consumer-owned <canvas>. Use this when the built-in toolbar isn't a fit and you need to render every UI affordance yourself. See CropControllerState, CropControllerActions, and CropControllerApi in src/react/use-sfx-crop-controller.ts.

Theming

Brand colour

The fastest way to recolour the editor is to override one variable on the host:

<sfx-crop style="--sfx-cr-primary:#ff3366"></sfx-crop>

CSS Custom Properties

Every visual surface is keyed off --sfx-cr-* tokens. The full list (see src/styles/shared.css.ts for the canonical defaults):

Colours

--sfx-cr-primary, --sfx-cr-primary-hover, --sfx-cr-primary-mid, --sfx-cr-primary-bg, --sfx-cr-primary-glow, --sfx-cr-success, --sfx-cr-error, --sfx-cr-text, --sfx-cr-text-secondary, --sfx-cr-text-muted, --sfx-cr-border, --sfx-cr-border-light, --sfx-cr-bg, --sfx-cr-surface, --sfx-cr-canvas-bg.

Canvas & frame

--sfx-cr-overlay-color, --sfx-cr-frame-color, --sfx-cr-frame-shadow, --sfx-cr-handle-fill, --sfx-cr-handle-stroke, --sfx-cr-ruler-ink, --sfx-cr-ruler-halo, --sfx-cr-ring, --sfx-cr-shadow.

--sfx-cr-ruler-ink and --sfx-cr-ruler-halo colour the fine-tilt ruler (ticks, centre indicator, degree readout). The ruler floats directly over the photo, whose brightness is unknown, so it can't track the theme: the ink defaults to a near-white core and the halo to a dark glow wrapped around it, so the white core reads over dark images while the halo reads over bright ones (the trick subtitles use). Override both together if you want a different ink/halo pairing.

Toolbar & controls

--sfx-cr-toolbar-bg, --sfx-cr-toolbar-color, --sfx-cr-toolbar-border, --sfx-cr-toolbar-shadow, --sfx-cr-btn-size, --sfx-cr-btn-radius, --sfx-cr-btn-hover-bg, --sfx-cr-btn-active-bg, --sfx-cr-separator-color, --sfx-cr-slider-track, --sfx-cr-slider-fill, --sfx-cr-slider-thumb, --sfx-cr-dropdown-bg, --sfx-cr-dropdown-hover, --sfx-cr-dropdown-shadow, --sfx-cr-zoom-bar-bg.

Typography & radius

--sfx-cr-font, --sfx-cr-radius, --sfx-cr-card-shadow, --sfx-cr-transition.

A [theme="dark"] selector on the host re-binds the same variables to the dark palette — no other configuration needed.

Shadow parts

Style internal regions from light DOM:

sfx-crop::part(toolbar) { /* ... */ }
sfx-crop::part(canvas-host) { /* ... */ }
sfx-crop::part(loading) { /* ... */ }
sfx-crop::part(error) { /* ... */ }
sfx-crop::part(container) { /* ... */ }

Types Reference

All types live in src/core/types.ts and are re-exported from both @scaleflex/image-crop and @scaleflex/image-crop/react.

  • CropShapeName'free' | 'square' | 'circle' | 'rounded-rect' | '16:9' | … plus any "W:H" string.
  • CropRect{ x, y, width, height } in normalised [0,1] image coordinates.
  • TransformState — full runtime state (quarterTurns, rotation, flipH, flipV, scale, panX/Y, cropRect, rotationPivot?).
  • TransformParams — serialisable export shape (rotation, flipH, flipV, scale, crop in original-image pixels, outputWidth, outputHeight).
  • CropIconOverrides — per-slot SVG-string overrides for toolbar icons.
  • SfxCropConfig — the internal config shape consumed by createCropController. Element attributes mirror this 1:1.

Browser Support

Latest two versions of Chrome, Firefox, Safari, and Edge. Requires Custom Elements v1, Canvas 2D, Pointer Events, ResizeObserver, and CSS container queries. No IE11 support.

Release

See CHANGELOG.md for version history. The project follows Semantic Versioning and the Keep a Changelog format. The full technical specification lives in SPECIFICATION.md.

npm scripts

| Script | Purpose | |---|---| | npm run dev | Vite dev server for the demo SPA at http://localhost:5173. | | npm run build | Build the bundle and the React wrapper. | | npm run build:bundle | Web-component bundle only (dist/). | | npm run build:react | React wrapper only (dist/react/). | | npm run build:demo | Production demo site. | | npm run typecheck | tsc --noEmit. | | npm test | Vitest run. | | npm run test:watch | Vitest watch. | | npm run test:coverage | Vitest with coverage. | | npm run lint | ESLint over src/ and tests/. |

Claude Code Integration

This repository ships rules and prompts that make Claude Code productive on the codebase out of the box.

Option 1: Project-level (recommended)

Drop a CLAUDE.md at the repo root describing the project conventions, then add project-scoped rules under .claude/ (gitignored). Anyone with Claude Code installed picks them up automatically when they cd into the repo.

Option 2: Global (personal)

Add personal rules at ~/.claude/CLAUDE.md so they apply across every project you open.

Usage

Once configured, run Claude Code from the repo root:

claude

Then ask things like "add a setFlipVertical() public method" or "explain how the controller settles after a 90° rotation" and Claude will follow the project's conventions.

License

MIT © 2026 Scaleflex