Git Product home page Git Product logo

svelte-easy-crop's Introduction

svelte-easy-crop

A Svelte component to crop images with easy interactions

This is a rewrite of react-easy-crop (https://github.com/valentinh/react-easy-crop).

version Monthly downloads gzip size MIT License PRs Welcome

svelte-easy-crop Demo

Demo

Features

  • Supports drag and zoom interactions
  • Provides crop dimensions as pixels and percentages
  • Supports any images format (JPEG, PNG, even GIF) as url or base 64 string
  • Mobile friendly

Installation

yarn add svelte-easy-crop

or

npm install svelte-easy-crop --save

Basic usage

The Cropper is styled with position: absolute to take the full space of its parent. Thus, you need to wrap it with an element that uses position: relative or the Cropper will fill the whole page.

<script>
  import Cropper from 'svelte-easy-crop'

  let image = '/images/dog.jpeg'
  let crop = { x: 0, y: 0 }
  let zoom = 1
</script>

<Cropper
 {image}
 bind:crop
 bind:zoom
 on:cropcomplete={e => console.log(e.detail)}
/>

Props

Prop Type Required Description
image string The image to be cropped.
crop { x: number, y: number } Position of the image. { x: 0, y: 0 } will center the image under the cropper.
zoom number Zoom of the image between minZoom and maxZoom. Defaults to 1.
aspect number Aspect of the cropper. The value is the ratio between its width and its height. The default value is 4/3
minZoom number Minimum zoom of the image. Defaults to 1.
maxZoom number Maximum zoom of the image. Defaults to 3.
cropShape 'rect' | 'round' Shape of the crop area. Defaults to 'rect'.
cropSize { width: number, height: number } Size of the crop area (in pixels). If you don't provide it, it will be computed automatically using the aspect prop and the image size.
showGrid boolean Whether to show or not the grid (third-lines). Defaults to true.
zoomSpeed number Multiplies the value by which the zoom changes. Defaults to 1.
crossOrigin string Allows setting the crossOrigin attribute on the image.
restrictPosition boolean Whether the position of the image should be restricted to the boundaries of the cropper. Useful setting in case of zoom < 1 or if the cropper should preserve all image content while forcing a specific aspect ratio for image throughout the application. Example: https://codesandbox.io/s/1rmqky233q.

Events

on:cropcomplete

This event is the one you should use to save the cropped area of the image. The detail property is an object with 2 values:

  1. percent: coordinates and dimensions of the cropped area in percentage of the image dimension
  2. pixels: coordinates and dimensions of the cropped area in pixels.

Both arguments have the following shape:

const area = {
  x: number, // x/y are the coordinates of the top/left corner of the cropped area
  y: number,
  width: number, // width of the cropped area
  height: number, // height of the cropped area
}

Development

yarn
yarn dev

Now, open http://localhost:5000 and start hacking!

License

MIT

svelte-easy-crop's People

Contributors

btglr avatar cotlod avatar davidonium avatar dependabot[bot] avatar drijnkels avatar ksawery29 avatar oscarhermoso avatar ronkeiser avatar tamaki-tech avatar valentinh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

svelte-easy-crop's Issues

Ability to control the colour, thickness and line type of the grid

The ability to control the colour, thickness and line type (solid, dotted, dashed) via properties on the <Cropper> would be useful.

For my implementation, I've worked around this by turning off the out-of-the-box grid and then providing my own CSS, but a simpler way of influencing this would be a welcome feature.

Many thanks!

Zooming doesn't work in Safari browser

Are you reporting a bug?

Basic example is relevant and bug could be reproduced -> https://codesandbox.io/s/svelte-easy-crop-basic-demo-q1005?file=/App.svelte. Also reproduces on react-easy-crop basic example.

Env:

  • Safari version -> Version 15.6.1 (17613.3.9.1.16)
  • macOS Montrey 12.5.1 (21G83)

Provide the steps to reproduce the issue, e.g.:

  • Open sandbox
  • Try to pinch by two fingers. I'm using a laptop touchpad for it mouse wheel, behaviour is same

Expected behaviour

Pinch works correctly in Safari

Support Sveltekit 2 which requires an exports condition in package.json

To recreate the issue, add svelte-easy-crop to a Sveltekit 2 project and npm install.

You will see the following warning:

[vite-plugin-svelte] WARNING: The following packages have a svelte field in their package.json but no exports condition for svelte.

Sveltekit 2 adds a dependency on vite-plugin-svelte, which requires an exports condition in package.json as described here under the "missing exports condition" header:

https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#missing-exports-condition

Currently, vite-plugin-svelte still resolves svelte-easy-crop as a fallback, but in a future release this will be removed and without the exports condition it will fail.

It may also be worth upgrading svelte-easy-crop to use Sveltekit 2 itself.

General UX Additions

For my project, I was planning on making some edits to the user experience of the cropper:

  1. Putting a blur background filter on the outside dark section, optionally
  2. Allowing the user to put their own "crop/submit" and "cancel" buttons under the highlighted area being cropped

For the custom buttons, I was thinking of using a slot, which might open up the possibility to add any kind of content under (and maybe also above) the cropper. For now, I'm planning on forking the repo and adding my edits there, but let me know if this sounds like a good idea to add to the main repo!

Build warning - A11y: <div> with mousedown handler must have an ARIA role

Since updating to Svelte 4.0, builds have this warning:

/node_modules/svelte-easy-crop/Cropper.svelte:205:0 A11y: <div> with mousedown handler must have an ARIA role

I haven't opened up the svelte-easy-crop repo yet, but I'm sure this warning will be visible in VS Code:

A11y: <div> with click, keypress, dragstart, dragend handlers must have an ARIA role (a11y-no-static-element-interactions)

I looked at what be a valid role, best attribute would probably be role="application" , but this doesn't actually resolve the warning

https://www.w3.org/TR/wai-aria-1.1/#application

Touching outside of the cropping window moves the image on mobile

I have a relative crop-container div surrounding the svelte-easy-crop component.

        <div id="crop-container" bind:this={container} class="m-3" bind:clientWidth={width} bind:clientHeight={height}>
            <Cropper
                image={context.image}
                crop={{ x: 0, y: 0 }}
                zoom={2}
                aspect={17/6}
                maxZoom={8}
                showGrid={false}
                cropSize={{
                    height: height - 20,
                    width: width - 20
                }}
                on:cropcomplete={e => onCrop(e.detail.pixels)}
                />
        </div>

I've been testing zooming and panning on both desktop (macOS) and mobile (iPhone).

Observed behaviour:

On desktop, if I click within the crop box and drag, it will pan the image. If I click outside the crop box (e.g. anywhere else on the web page), it doesn't interact with the image or cropper. This is also what I'd expect to happen.

However, on mobile, holding and dragging with one finger pans the image like with desktop, however trying to scroll the web page by pressing and dragging outside of the crop box results in the image panning. This is inconsistent with desktop and not what I'd expect to happen. More frustratingly, I can't scroll up or down the page anymore.

Expected behaviour:

Interacting with the web page outside of the crop box doesn't impact the image or cropping area.

Is this behaviour by design or is it a bug? If it's by design, is there a way in which I can accomplish what I' trying to do?

Thanks in advance!

Struggling to get a specific look and feel for the cropper

I have a modal where I can upload an avatar, with a button to then re-crop the avatar, like so:

Screen Shot 2021-03-24 at 22 44 24

When I press that left "crop" button, I show the Cropper component, with the same size and height as the avatar itself, but sadly it looks weird:

Screen Shot 2021-03-24 at 22 44 36

The code is like this:

<script>
  let showCropper = false;
  let pixels;

  function saveCrop() {
    // ...
  }
</script>

<style>
  .avatar-container, .cropper-container {
    width: 150px;
    height: 150px;
    position: relative;
    margin: 0 auto 1.25rem auto;
    overflow: hidden;
    border-radius: 50%;
  }

  .button-container {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    text-align: center;
    padding: 5px;
  }
</style>

{#if !showCropper}
  <div class="avatar-container">
    <Avatar character={{fields: $form}} size="150" />

    <div class="button-container">
      <button title="Change avatar crop" type="button" class="as-link" on:click={() => showCropper = true}>
        <span class="icon icon-crop"></span>
      </button>

      <button type="button" class="as-link" on:click={() => $form.avatar = null}>
        <span class="icon icon-delete"></span>
      </button>
    </div>
  </div>
{/if}  

{#if showCropper && $form.avatar}
  <div class="cropper-container">
    <Cropper 
    image={$form.avatar} 
    aspect=1 
    cropShape="round" 
    cropSize={{width: 150, height: 150}}
    showGrid={false} 
    on:cropcomplete={e => pixels = e.detail.pixels} />

    <div class="button-container">
      <button class="as-link" type="button" on:click={() => showCropper = false}>
        <span class="icon icon-cancel"></span>
      </button>

      <button class="as-link" type="button" on:click={saveCrop}>
        <span class="icon icon-save"></span>
      </button>
    </div>
  </div>
{/if}

So the weird thing is that the image doesn't start out zoomed such that it fills the entire circle, and the crop doesn't match the original image. By contrast, if I remove cropSize from the Cropper, the image does fill the circle and the crop matches the original (i.e. the center of the image), but this is not how I want the cropper to look...

Screen Shot 2021-03-24 at 22 46 03

Finally, I am not able to make the cropper look like a circle, which is what I'd really like. This css didn't help:

.cropper-container :global(.container) {
  overflow: hidden;
  border-radius: 50%;
}

So, I guess I have two questions:

  1. How can I make the cropper start out with the image zoomed such that it fills the crop area? The contents of the circle should like like the 3rd screenshot, but I want to set the size of the circle.
  2. How I can hide everything outside of the circle? The goal is to seamlessly switch between the avatar and the cropper, both look like a circle with some buttons at the bottom.

Error: 'add_binding_callback' is not exported from svelte/internal

Hi, I was trying to add this to my svelte app but I got this error when trying to run the dev npm script.

bundles src/main.js → public/build/bundle.js...
[!] Error: 'add_binding_callback' is not exported by node_modules/svelte/internal/index.mjs, imported by node_modules/svelte-easy-crop/dist/index.mjs
https://rollupjs.org/guide/en/#error-name-is-not-exported-by-module
node_modules/svelte-easy-crop/dist/index.mjs (1:127)
1: import { SvelteComponent, init, safe_not_equal, element, append, set_style, toggle_class, insert, detach, space, attr, listen, add_binding_callback, noop, run_all } from 'svelte/internal';
                                                                                                                                  ^
2: import { createEventDispatcher, onMount, onDestroy } from 'svelte';
Error: 'add_binding_callback' is not exported by node_modules/svelte/internal/index.mjs, imported by node_modules/svelte-easy-crop/dist/index.mjs
    at error (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:217:30)
    at Module.error (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:15145:16)
    at handleMissingExport (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:15042:28)
    at Module.traceVariable (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:15520:24)
    at ModuleScope.findVariable (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:14199:39)
    at FunctionScope.findVariable (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:9663:38)
    at ChildScope.findVariable (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:9663:38)
    at FunctionScope.findVariable (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:9663:38)
    at ChildScope.findVariable (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:9663:38)
    at Identifier$1.bind (/Users/kenanchristian/Documents/projects/svelte-app/node_modules/rollup/dist/shared/rollup.js:10080:40)

I'm using the svelte default template, and only adding svelte-preprocess to compile sass stylesheet, and added a babel config with babel-preset-env.

Start the cropper with cropdata

I want to mount the cropper with the crop settings that the users saved before.
The crop results are saved and returned by my api.

But the crop property only accepts x and y not height and width.

Expected behavior
The crop property can accept a value like this:

{"width": 2617, "height": 2617, "x": 848, "y": 568}

The cropper uses these values as the start values

A full demo with a file upload field

Congratulations for this component.

I just thought it would be really appealing to show the component in a real world situation with a file upload field:

  • the user chooses a picture
  • the user crops the picture
  • the user sends the form (with the cropped picture)

What do you think?

typescript support

Hey, I'm trying to use it with typescript, but I'm getting this error:

Error: Parameter 'e' implicitly has an 'any' type. (ts)
        {cropSize}
        on:cropcomplete={(e) => {
                pixelCrop.set(e.detail.pixels);

Will there ever be typescript support?

Precise cropping?

My mouse wheel is configured to scroll three lines per tick. I'm sure I'm not the only one who has it set to 3+ lines, since scrolling by 1–2 lines is painfully slow, especially on large screens.

As a result, svelte-easy-crop crop in very large discrete steps, often missing desired dimensions.

There is the zoomSpeed property, but if I tweak it to work for me, it will be messed up for users using different scrolling preferences.

I've looked at other JS cropping libraries, and the absolute majority of them do not have this problem thanks to being able to draw the rectangle with mouse and/or reslize the rectangle by dragging on its edges/corners. Naturally, mouse movement is much more precise than mouse wheel / pinch zooming.

Screenshots from other libraries

image
image
image
image
image
image
image
image

How to get a full-crop-box

I'm trying to get a full-crop-box like https://fengyuanchen.github.io/cropperjs/examples/full-crop-box.html

With this code it seems to work...

<div style="width: 200px; height: 200px; position: relative;">
    <Cropper
      image="https://cdn1-www.dogtime.com/assets/uploads/2011/03/puppy-development.jpg"
      crop={{ x: 0, y: 0 }}
      zoom={1}
      aspect="1"
      cropSize={{ width: 200, height: 200 }}
      restrictPosition={true} />
</div>

..and I get this image
image

When I scroll (minus) the picture, it is no longer centered.
In this case the event on:cropcomplete will not be fired.
I think 'crop' will not be updated correctly.
image

Does anyone have an idea whether it is a limitation or a bug?

Error during SSR due to `onDestroy` / `cleanEvents` being called

Error during SSR due to cleanEvents being called.

  • Provide the steps to reproduce the issue, e.g.:

    1. Create a svelte-kit page containing Cropper component
    2. Somehow destroy/move that component to a different location during SSR (for example, in my specific use case, the Cropper component exists in a modal div that needs to be appended to the document root)
    3. onDestroy() fires which calls cleanEvents(), resulting in a document is not defined error when attempting to remove the event listeners.

onDestroy firing during SSR is expected behaviour, see sveltejs/kit#945

I'll raise a PR to offer a fix for the issue 🙂

[svelte] ReferenceError: document is not defined
[svelte]     at cleanEvents (/node_modules/svelte-easy-crop/src/index.svelte:61:3)
[svelte]     at eval (/node_modules/svelte-easy-crop/src/index.svelte:57:3)
[svelte]     at run (/node_modules/svelte/internal/index.mjs:20:12)
[svelte]     at Array.forEach (<anonymous>)
[svelte]     at run_all (/node_modules/svelte/internal/index.mjs:26:9)
[svelte]     at Object.render (/node_modules/svelte/internal/index.mjs:1882:13)
[svelte]     at render_response (file:///home/oscarhermoso/Git/frontend/node_modules/@sveltejs/kit/src/runtime/server/page/render.js:119:27)
[svelte]     at runMicrotasks (<anonymous>)
[svelte]     at processTicksAndRejections (node:internal/process/task_queues:96:5)
[svelte]     at async render_page (file:///home/oscarhermoso/Git/frontend/node_modules/@sveltejs/kit/src/runtime/server/page/index.js:277:10)

Add a slider to control the zoom

It's a feature request, I suppose, but it would be very useful to add an optional slider in order to control the zoom (there is one on the React version). Otherwise the component is not usable on laptops, since there is no mouse wheel.

Changing the `aspect` value updates the crop area but does not trigger `on:cropcomplete`

Reproduction:
lolmaus/slice-and-dice@61180d0

Steps to reproduce:

  1. Upload an image (it's not sent to any backend), you will immediately see the crop result below the Cropper.
  2. Change the aspect ratio.

Expected result: the crop area should update, reflecting the new aspect ratio. The crop result should update accordingly.

Actual result: the crop area updates, reflecting the new aspect ratio. But the crop result does not update and still uses the old aspect ratio, because on:cropcomplete was not called.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.