Skip to content

Commit 611b082

Browse files
committed
Add the posibility to leave the image boundaries by stencil
1 parent 4d2df82 commit 611b082

File tree

8 files changed

+324
-69
lines changed

8 files changed

+324
-69
lines changed

example/docs/.vuepress/components/custom-restrictions-example.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ export default {
2020
return {
2121
minWidth: minWidth,
2222
minHeight: minHeight,
23-
maxWidth: Math.min(imageWidth, maxWidth),
24-
maxHeight: Math.min(imageHeight, maxHeight),
23+
maxWidth: maxWidth,
24+
maxHeight: maxHeight,
2525
};
2626
},
2727
onCrop({ canvas, }) {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<script>
2+
import { RoundStencil, Cropper } from 'vue-advanced-cropper';
3+
4+
export default {
5+
components: {
6+
Cropper,
7+
},
8+
data() {
9+
return {
10+
image: 'https://images.pexels.com/photos/3304973/pexels-photo-3304973.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260',
11+
restrictionType: 'none',
12+
result: null,
13+
};
14+
},
15+
methods: {
16+
onCrop({ canvas, }) {
17+
this.result = canvas.toDataURL();
18+
},
19+
showImage() {
20+
const newTab = window.open();
21+
newTab.document.body.innerHTML = `<img src="${this.result}"></img>`;
22+
},
23+
},
24+
};
25+
</script>
26+
27+
<template>
28+
<div class="image-restriction-example">
29+
<Cropper
30+
check-orientation
31+
backgroundClassname="background"
32+
:src="image"
33+
:image-restriction="restrictionType"
34+
@change="onCrop"
35+
/>
36+
<div class="panel">
37+
<div class="panel__left">
38+
<div class="input">
39+
<span class="input__label">Image Restriction Type</span>
40+
<select
41+
v-model="restrictionType"
42+
class="input__control"
43+
type="text"
44+
>
45+
<option value="area">area</option>
46+
<option value="stencil">stencil</option>
47+
<option value="none">none</option>
48+
</select>
49+
</div>
50+
</div>
51+
<div class="panel__right">
52+
<div
53+
v-if="this.result"
54+
class="button"
55+
@click="showImage()"
56+
>
57+
Download
58+
</div>
59+
</div>
60+
</div>
61+
</div>
62+
</template>
63+
64+
<style lang="scss">
65+
.image-restriction-example {
66+
margin-top: 20px;
67+
margin-bottom: 20px;
68+
69+
.background {
70+
background: #C6BFAF;
71+
}
72+
73+
.panel {
74+
color: white;
75+
display: flex;
76+
align-items: flex-end;
77+
padding: 20px;
78+
background: #3fb37f;
79+
&__left {
80+
width: 100%;
81+
padding-right: 30px;
82+
}
83+
}
84+
85+
.input {
86+
&__control {
87+
padding: 8px;
88+
width: 100%;
89+
border: none;
90+
color: black;
91+
font: inherit;
92+
font-size: 15px;
93+
}
94+
&__label {
95+
display: block;
96+
font-size: 11px;
97+
margin-bottom: 5px;
98+
}
99+
}
100+
.image-restriction-cropper {
101+
width: 100%;
102+
max-height: 500px;
103+
border: solid 1px #EEE;
104+
}
105+
106+
.button {
107+
width: 120px;
108+
margin-top: 15px;
109+
display: block;
110+
color: white;
111+
font-size: 16px;
112+
padding: 10px 20px;
113+
text-align: center;
114+
background: #1f8255;
115+
cursor: pointer;
116+
transition: background 0.5s;
117+
font-weight: normal;
118+
&:hover {
119+
background: #26a069;
120+
text-decoration: none !important;
121+
}
122+
input {
123+
display: none;
124+
}
125+
}
126+
}
127+
</style>

example/docs/tutorials/recipes.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ new Vue({
284284
return {
285285
minWidth: minWidth,
286286
minHeight: minHeight,
287-
maxWidth: Math.min(imageWidth, maxWidth),
288-
maxHeight: Math.min(imageHeight, maxHeight),
287+
maxWidth: maxWidth,
288+
maxHeight: maxHeight,
289289
}
290290
},
291291
},
@@ -340,6 +340,16 @@ export default {
340340
</div>
341341
```
342342

343+
## Different image restrictions
344+
345+
You are able to set different the restrictions of an image position by passing the following string to the `imageRestriction` prop:
346+
- `area` (default) prevents resizing and moving the image beyond the area
347+
- `stencil` prevents resizing and moving the image beyond the stencil
348+
- `none` allows free resizing and moving the image
349+
350+
<image-restrictions-example></image-restrictions-example>
351+
352+
343353
## Set coordinates
344354

345355
Usually an user changes the coordinates of a stencil, but sometimes you need to set its coordinates programmatically. There is the special method to do it: [setCoordinates](/components/cropper.html#setcoordinates-transform). It applies your changes respect to existing limitation (aspect ratios, minimum size and etc.)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue-advanced-cropper",
3-
"version": "0.14.2",
3+
"version": "0.15.0",
44
"description": "The advanced library to create your own croppers suited for any website design",
55
"author": "Norserium",
66
"license": "MIT",

src/Cropper.vue

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import debounce from 'debounce';
66
import { RectangleStencil } from './components/stencils';
77
import { CropperWrapper } from './components/service';
88
import { ResizeEvent, MoveEvent } from './core/events';
9-
import { isLocal, isCrossOriginURL, isUndefined, addTimestamp, getSettings } from './core/utils';
9+
import { isLocal, isCrossOriginURL, isUndefined, addTimestamp, getSettings, parseNumber } from './core/utils';
1010
import { arrayBufferToDataURL, getImageTransforms, getStyleTransforms, prepareSource, parseImage } from './core/image';
11-
import { ALL_DIRECTIONS, MINIMAL_PERCENT_SIZE } from './core/constants';
11+
import { ALL_DIRECTIONS, MINIMAL_PERCENT_SIZE, IMAGE_RESTRICTIONS } from './core/constants';
1212
import * as algorithms from './core/algorithms';
1313
1414
const cn = bem('vue-advanced-cropper');
@@ -41,6 +41,10 @@ export default {
4141
type: String,
4242
default: null,
4343
},
44+
allowedArea: {
45+
type: Function,
46+
default: algorithms.allowedArea,
47+
},
4448
resizeAlgorithm: {
4549
type: Function,
4650
default: algorithms.resize,
@@ -140,6 +144,9 @@ export default {
140144
imageRestriction: {
141145
type: [String],
142146
default: 'area',
147+
validator(value) {
148+
return IMAGE_RESTRICTIONS.indexOf(value) !== -1;
149+
}
143150
}
144151
},
145152
data() {
@@ -199,7 +206,7 @@ export default {
199206
};
200207
201208
// Disable some interactions for user convenience
202-
settings.touchMove.enabled = settings.touchMove.enabled && this.worldTransforms.scale > 1;
209+
settings.touchMove.enabled = settings.touchMove.enabled && (this.worldTransforms.scale !== 1 || this.imageRestriction === 'none');
203210
204211
return settings;
205212
},
@@ -265,13 +272,20 @@ export default {
265272
return result;
266273
},
267274
stencilRestrictions() {
275+
const oldRestrictions = {
276+
minWidth: !isUndefined(this.minWidth) ? this.minWidth : 0,
277+
minHeight: !isUndefined(this.minHeight) ? this.minHeight : 0,
278+
maxWidth: !isUndefined(this.maxWidth) ? this.maxWidth : Infinity,
279+
maxHeight: !isUndefined(this.maxHeight) ? this.maxHeight : Infinity,
280+
};
281+
268282
const restrictions = migrateAlgorithm(this.restrictions, 'restrictions', (args) => [
269283
args.minWidth, args.minHeight, args.maxWidth, args.maxHeight, args.imageWidth, args.imageHeight
270284
])({
271-
minWidth: Number(this.minWidth),
272-
minHeight: Number(this.minHeight),
273-
maxWidth: Number(this.maxWidth),
274-
maxHeight: Number(this.maxHeight),
285+
minWidth: parseNumber(oldRestrictions.minWidth),
286+
minHeight: parseNumber(oldRestrictions.minHeight),
287+
maxWidth: parseNumber(oldRestrictions.maxWidth),
288+
maxHeight: parseNumber(oldRestrictions.maxHeight),
275289
imageWidth: this.imageSize.width,
276290
imageHeight: this.imageSize.height,
277291
props: this.$props
@@ -302,7 +316,7 @@ export default {
302316
restrictions.heightFrozen = true;
303317
}
304318
305-
if (this.imageRestriction === 'area') {
319+
if (this.imageRestriction !== 'none') {
306320
if (!restrictions.maxWidth || (restrictions.maxWidth > this.imageSize.width)) {
307321
restrictions.maxWidth = this.imageSize.width;
308322
}
@@ -330,6 +344,9 @@ export default {
330344
maxHeight() {
331345
this.onPropsChange();
332346
},
347+
imageRestriction() {
348+
this.resetCoordinates();
349+
},
333350
stencilProps(oldProps, newProps) {
334351
const significantProps = ['aspectRatio', 'minAspectRatio', 'maxAspectRatio'];
335352
if (significantProps.find(prop => oldProps[prop] !== newProps[prop])) {
@@ -386,6 +403,7 @@ export default {
386403
worldTransforms: this.worldTransforms,
387404
coefficient: this.coefficient,
388405
imageSize: this.imageSize,
406+
allowedArea: this.getAllowedArea(true)
389407
});
390408
391409
this.worldTransforms = worldTransforms;
@@ -497,7 +515,7 @@ export default {
497515
this.resizeAlgorithm({
498516
coordinates: this.coordinates,
499517
restrictions: this.stencilRestrictions,
500-
allowedArea: this.allowedArea(),
518+
allowedArea: this.getAllowedArea(),
501519
aspectRatio: this.stencilAspectRatios(),
502520
resizeEvent
503521
})
@@ -520,6 +538,9 @@ export default {
520538
frozenDirections: this.frozenDirections,
521539
stencilCoordinates: this.stencilCoordinates,
522540
worldTransforms: this.worldTransforms,
541+
allowedArea: this.getAllowedArea(true),
542+
minScale: this.imageRestriction === 'none' ? MINIMAL_PERCENT_SIZE : 1,
543+
fitImage: this.imageRestriction === 'area'
523544
});
524545
this.worldTransforms = worldTransforms;
525546
this.onChangeCoordinates(coordinates);
@@ -532,7 +553,7 @@ export default {
532553
this.onChangeCoordinates(
533554
this.moveAlgorithm({
534555
coordinates: this.coordinates,
535-
allowedArea: this.allowedArea(),
556+
allowedArea: this.getAllowedArea(),
536557
moveEvent
537558
})
538559
);
@@ -590,12 +611,8 @@ export default {
590611
},
591612
applyTransforms(transforms, autoZoom = false) {
592613
const aspectRatio = this.stencilAspectRatios();
593-
const allowedArea = {
594-
left: 0,
595-
top: 0,
596-
right: this.imageSize.width,
597-
bottom: this.imageSize.height
598-
};
614+
615+
const allowedArea = this.getAllowedArea(true);
599616
600617
const moveAlgorithm = (prevCoordinates, newCoordinates) => {
601618
return this.moveAlgorithm({
@@ -649,7 +666,7 @@ export default {
649666
}
650667
});
651668
652-
if (autoZoom && this.worldTransforms.scale > 1) {
669+
if (autoZoom) {
653670
this.autoZoom(coordinates);
654671
} else {
655672
if (this.worldTransforms.scale > 1) {
@@ -791,13 +808,13 @@ export default {
791808
}
792809
});
793810
},
794-
allowedArea() {
795-
return {
796-
left: -this.worldTransforms.shift.left / this.worldTransforms.scale,
797-
top: -this.worldTransforms.shift.top / this.worldTransforms.scale,
798-
right: (this.boundarySize.width - this.worldTransforms.shift.left / this.coefficient) / this.worldTransforms.scale * this.coefficient,
799-
bottom: (this.boundarySize.height - this.worldTransforms.shift.top / this.coefficient) / this.worldTransforms.scale * this.coefficient,
800-
};
811+
getAllowedArea(breakBoundaries) {
812+
return this.allowedArea({
813+
breakBoundaries,
814+
imageSize: this.imageSize,
815+
worldTransforms: this.worldTransforms,
816+
imageRestriction: this.imageRestriction
817+
});
801818
},
802819
stencilAspectRatios() {
803820
if (this.$refs.stencil.aspectRatios) {

0 commit comments

Comments
 (0)