A crop window component for images and videos that supports touch gestures (pinch zoom, rotate, pan), as well as mousewheel zoom, mouse-dragging the image, and rotating on right mouse button.
Currently looking for contributors / feature requests / feedback to help improve this component.
If you can do code-review, that's very welcome.
Here's a demo page.
And here's a minimal REPL where you can play with the code and a fancier REPL.
npm install svelte-crop-window
You must wrap the CropWindow
component with an Element that determines the height.
<script>
import { CropWindow, defaultValue } from 'svelte-crop-window';
let media = {
content_type: 'image',
url: '/svelte-crop-window/hintersee-3601004.jpg'
};
let value = { ...defaultValue };
</script>
<div style="height:20em">
<CropWindow bind:value {media} />
</div>
CropWindow.svelte
Componentname | type | required | purpose |
---|---|---|---|
media |
Media |
✓ | image or video to be cropped |
value |
CropValue |
value that describes how to crop - will be initialized if undefined | |
options |
Options |
options for the crop window and overlay, see below |
type Media = {
content_type: 'image' | 'video';
url: string;
}
type CropValue = {
position: { x: number; y: number };
aspect: number;
rotation: number;
scale: number; }
}
const defaultValue: CropValue = {
position: { x: 0, y: 0 },
aspect: 1.0,
rotation: 0,
scale: 0
};
name | type | purpose |
---|---|---|
shape |
'rect' | 'round' |
shape of the crop area (yes, an ellipse will work) |
crop_window_margin |
number |
Margin of the crop window, in pixels. The crop window will always scale to the maximum possible size in its containing element. |
overlay |
a Svelte component | The overlay component which visually highlights the crop area. You can pass your own Svelte component with props options: T, gesture_in_progress: boolean, shape: 'rect' | 'round' here, or use the included Overlay.svelte. |
overlay_options |
T |
Options for your overlay component. See below for the options of the included overlay component. |
const defaultOptions: Options<OverlayOptions> = {
shape: 'rect',
crop_window_margin: 10,
overlay: Overlay,
overlay_options: defaultOverlayOptions
};
Overlay.svelte
Componentname | type | purpose |
---|---|---|
overlay_color |
string |
the color of the overlay that covers everything except the crop area |
line_color |
string |
the color of the lines |
show_third_lines |
boolean |
whether to show third lines or not when a gesture is in progress |
const defaultOverlayOptions: OverlayOptions = {
overlay_color: 'rgb(11, 11, 11, 0.7)',
line_color: 'rgba(167, 167, 167, 0.5)',
show_third_lines: true
};
<div
style="
position:relative; overflow:hidden;
height:{HEIGHT}px; width:{value.aspect * HEIGHT}px;
border-radius: {options.shape == 'round' ? '50%' : '0'}"
>
<video
style="
transform: translateX(-50%) translateY(-50%) rotate({value.rotation}deg);
height: {value.scale * HEIGHT}px;
margin-left: {HEIGHT * value.aspect / 2 + value.position.x * HEIGHT}px;
margin-top: {HEIGHT / 2 + value.position.y * HEIGHT}px;
max-width:none"
src="{url}"
/>
</div>
Note: You must choose a HEIGHT
, because the crop value is normalized against the target height.
target_height
and calculate the target_width
for the cropped image:let target_width = value.aspect * target_height;
s
by which to scale:let s = (value.scale * target_height) / media.height;
s
:let resized_media = scale(media, s);
value.rotation
:let resized_and_rotated_media = rotate(resized_media, value.rotation);
let left = (resized_and_rotated_media.width - target_width) / 2.0
- value.x * target_height;
let top = (resized_and_rotated_media.height - target_height) / 2.0
- value.y * target_height;
let cropped_media =
extract_area(resized_and_rotated_media,
left, top, target_width, target_height);
Once you've cloned the project and installed dependencies with npm install
(or pnpm install
or yarn
), start a development server:
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
One big inspiration for this component was the Android library uCrop by Yalantis. What is particularly valuable is that the developers shared their thought process in this blog post.
Another very helpful resource was svelte-easy-crop which gave me a basic understanding of how to implement a crop window component in Svelte (and HTML/JS in general).
There's no code reuse between either of these components and this one. All calculations had to be recreated from textbook math.