A crop window with touch and mouse gestures to zoom, pan and rotate an image or video.
Media snaps back to cover the crop area.
Looking for contributions, code review and feedback! Feel free to open an issue on the GitHub repository or connect with me via the Svelte discord (sabine#8815).
This is still in an experimental stage. Changes to the API are to be expected. After v1.0.0, this package will strictly follow semantic versioning.
The code of this demo page is here.
// in script tag
import { CropWindow, defaultValue } from 'svelte-crop-window';
let media: Media = {
content_type: 'image',
url: '/svelte-crop-window/hintersee-3601004.jpg'
};
let value = { ...defaultValue };
// in the template
<div style="height:20em; background: #222">
<CropWindow bind:value {media} />
</div>
<div style="height:20em">
<CropWindow
bind:value
{media}
{options}
/>
</div>
const defaultOverlayOptions: OverlayOptions = {
overlay_color: '#222222',
line_color: '#FFFFFF',
show_third_lines: true,
};
const defaultOptions: Options<OverlayOptions> = {
shape: 'rect',
crop_window_margin: 10,
overlay: Overlay,
overlay_options: defaultOverlayOptions,
}
const defaultValue: CropValue = {
position: { x: 0, y: 0 },
aspect: 1.0,
rotation: 0,
scale: 0
};
<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 || 0}deg);
height: {(value.scale || 0.0) * HEIGHT}px;
margin-left: {HEIGHT * value.aspect / 2 + (value.position.x || 0) * HEIGHT}px;
margin-top: {HEIGHT / 2 + (value.position.y || 0) * HEIGHT}px;
max-width:none`}
src={url}
/>
</div>
target_height
and calculate the width for the cropped image:
let target_width = target_height * value.aspect;
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);
Similar to the overlay, it would be nice to include some controls to make this more usable out of the box. Contributions are very welcome.
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 being reused between either of these components and this one (all calculations had to be recreated from textbook math).
Video from shantararam at pixabay: mountain-nature-snow-old-mountain-8837