@behance/helicropter
Advanced tools
Comparing version 15.5.5 to 15.6.0
@@ -30,4 +30,4 @@ import $ from 'jquery'; | ||
}, | ||
viewportRatio: 'static', | ||
allowTransparency: false, | ||
viewportRatio: 'dynamic', | ||
allowTransparency: true, | ||
// uploadTitle: 'Upload a new cover image', | ||
@@ -34,0 +34,0 @@ // uploadSubtitle: 'This will not affect your Behance cover image', |
{ | ||
"name": "@behance/helicropter", | ||
"version": "15.5.5", | ||
"version": "15.6.0", | ||
"description": "My helicropter goes \"whosh whosh whosh\"", | ||
@@ -5,0 +5,0 @@ "main": "src/js/index.js", |
@@ -18,17 +18,24 @@ import View from '@behance/beff/View'; | ||
this._$canvas = this.$view.find('.js-cropper-canvas'); | ||
this._$canvas.prop({ | ||
this.canvasCss = { | ||
width: this._model.canvasWidth, | ||
height: this._model.canvasHeight, | ||
}); | ||
}; | ||
const canvasContainerClass = 'js-canvas-container'; | ||
this._$canvas.prop(this.canvasCss); | ||
this.canvasOrder = ['background', 'foreground']; | ||
const containerClass = 'js-canvas-container'; | ||
this._canvas = new fabric.Canvas(this._$canvas[0], { | ||
selection: false, | ||
containerClass: canvasContainerClass, | ||
containerClass, | ||
}); | ||
this.$canvasContainer = this.$view.find(`.${canvasContainerClass}`); | ||
this.$cropperCanvas = this.$view.find('.js-cropper-canvas'); | ||
this.$canvasContainer.css('transform-origin', 'left top'); | ||
this.$canvasContainer = this.$view.find(`.${containerClass}`); | ||
this.$canvasContainer.css({ | ||
transformOrigin: 'left top', | ||
zIndex: this.canvasOrder.indexOf('foreground'), | ||
}); | ||
@@ -54,4 +61,2 @@ // IMPORTANT: Using undocumented Fabric.js API here to force retina-like behavior | ||
this._createTransparencyBackground(); | ||
if (this._model.viewportRatio === 'static') { | ||
@@ -76,3 +81,5 @@ this._createStaticCropArea(); | ||
'scale-view'({ scale }) { | ||
this._scaleView({ scale }); | ||
this.scale = scale; | ||
this._scaleView({ $el: this.$canvasContainer, scale }); | ||
this._scaleView({ $el: this.$backgroundContainer, scale }); | ||
}, | ||
@@ -85,2 +92,4 @@ | ||
this._createBackground().then(() => this.trigger('background-loaded')); | ||
this.trigger('cropper-set-image'); | ||
@@ -155,5 +164,5 @@ }, | ||
return { | ||
x: Math.max(0, (this._getImageProp('left') * -1) + this._getCropAreaProp('left')), | ||
y: Math.max(0, (this._getImageProp('top') * -1) + this._getCropAreaProp('top')), | ||
const retVal = { | ||
x: this._getImageProp('left') * -1 + this._getCropAreaProp('left'), | ||
y: this._getImageProp('top') * -1 + this._getCropAreaProp('top'), | ||
width: Math.max(1, this._cropArea.width), | ||
@@ -163,2 +172,7 @@ height: Math.max(1, this._cropArea.height), | ||
}; | ||
return Object.assign({}, retVal, { | ||
x: this._model.allowLetterboxing ? retVal.x : Math.max(0, retVal.x), | ||
y: this._model.allowLetterboxing ? retVal.y : Math.max(0, retVal.y), | ||
}); | ||
}, | ||
@@ -170,4 +184,2 @@ | ||
const { x, y, width, height, scale } = this.getCropData(); | ||
const nativeWidth = this._getImageProp('width'); | ||
const nativeHeight = this._getImageProp('height'); | ||
@@ -181,3 +193,22 @@ const scaledValues = { | ||
const reductionRatio = this._getReductionRatio(scaledValues); | ||
scaledValues.x = Math.floor(scaledValues.x * reductionRatio); | ||
scaledValues.y = Math.floor(scaledValues.y * reductionRatio); | ||
scaledValues.width = Math.floor(scaledValues.width * reductionRatio); | ||
scaledValues.height = Math.floor(scaledValues.height * reductionRatio); | ||
return scaledValues; | ||
}, | ||
_getReductionRatio(scaledValues) { | ||
let reductionRatio = 1; | ||
if (this._model.allowLetterboxing) { | ||
return reductionRatio; | ||
} | ||
const nativeWidth = this._getImageProp('width'); | ||
const nativeHeight = this._getImageProp('height'); | ||
if (scaledValues.width > nativeWidth) { | ||
@@ -190,12 +221,22 @@ reductionRatio = nativeWidth / scaledValues.width; | ||
scaledValues.x = Math.floor(scaledValues.x * reductionRatio); | ||
scaledValues.y = Math.floor(scaledValues.y * reductionRatio); | ||
scaledValues.width = Math.floor(scaledValues.width * reductionRatio); | ||
scaledValues.height = Math.floor(scaledValues.height * reductionRatio); | ||
return reductionRatio; | ||
}, | ||
return scaledValues; | ||
_createBackground() { | ||
switch (this._model.backgroundType) { | ||
case 'solid': | ||
return this._createSolidBackground(); | ||
case 'image': | ||
return this._createBlurryImageBackground(); | ||
default: | ||
return this._createTransparencyBackground(); | ||
} | ||
}, | ||
_scaleView({ scale }) { | ||
this.$canvasContainer.css({ | ||
_scaleView({ $el, scale }) { | ||
if (!$el || !$el.length) { | ||
return; | ||
} | ||
$el.css({ | ||
transform: `scale(${scale})`, | ||
@@ -205,3 +246,3 @@ }); | ||
requestAnimationFrame(() => { | ||
const boundRect = this.$cropperCanvas[0].getBoundingClientRect(); | ||
const boundRect = this._$canvas[0].getBoundingClientRect(); | ||
let width = boundRect.width; | ||
@@ -211,7 +252,7 @@ let height = boundRect.height; | ||
if (!boundRect.width || !boundRect.height) { | ||
width = Number(this.$cropperCanvas.width()) * scale; | ||
height = Number(this.$cropperCanvas.height()) * scale; | ||
width = Number(this._$canvas.width()) * scale; | ||
height = Number(this._$canvas.height()) * scale; | ||
} | ||
this.$canvasContainer.css({ | ||
$el.css({ | ||
width, | ||
@@ -240,2 +281,85 @@ height, | ||
_createBlurryImageBackground() { | ||
if (!this._model.image) { | ||
return; | ||
} | ||
const containerClass = 'js-background-canvas-container'; | ||
const canvasClass = 'js-background-canvas'; | ||
const foregroundContainerClass = this._canvas.containerClass; | ||
this.$view.find(`.${foregroundContainerClass}`).after(`<canvas class="${canvasClass}"></canvas>`); | ||
const $backgroundCanvas = this.$view.find(`.${canvasClass}`); | ||
$backgroundCanvas.prop(this.canvasCss); | ||
this._backgroundCanvas = new fabric.Canvas($backgroundCanvas[0], { | ||
selection: false, | ||
containerClass, | ||
}); | ||
this.$backgroundContainer = this.$view.find(`.${containerClass}`); | ||
this.$backgroundContainer.css({ | ||
position: 'absolute', | ||
zIndex: this.canvasOrder.indexOf('background'), | ||
left: 0, | ||
top: 0, | ||
transformOrigin: 'left top', | ||
}); | ||
return this._loadImage(this._model.image) | ||
.then((image) => { | ||
const backgroundImage = new fabric.Image(image, { | ||
left: 0, | ||
top: 0, | ||
originX: 'left', | ||
originY: 'top', | ||
selectable: false, | ||
hasBorders: false, | ||
hasControls: false, | ||
}); | ||
this._backgroundCanvas.add(backgroundImage); | ||
backgroundImage.canvas.contextContainer.filter = 'blur(70px) brightness(.8)'; | ||
const isLandscape = backgroundImage.width > backgroundImage.height; | ||
if (isLandscape) { | ||
backgroundImage.scaleToHeight(this._canvas.height); | ||
} | ||
else { | ||
backgroundImage.scaleToWidth(this._canvas.width); | ||
} | ||
backgroundImage.sendToBack(); | ||
backgroundImage.center(); | ||
this._scaleView({ $el: this.$backgroundContainer, scale: this.scale }); | ||
}); | ||
}, | ||
_createSolidBackground() { | ||
return new Promise(resolve => { | ||
const solidRect = new fabric.Rect({ | ||
left: 0, | ||
top: 0, | ||
width: this._model.canvasWidth, | ||
height: this._model.canvasHeight, | ||
fill: this._model.backgroundHex, | ||
selectable: false, | ||
evented: false, | ||
hasBorders: false, | ||
hasControls: false, | ||
}); | ||
this._canvas.add(solidRect); | ||
solidRect.sendToBack(); | ||
resolve(); | ||
}); | ||
}, | ||
_createTransparencyBackground() { | ||
@@ -293,3 +417,4 @@ return this._loadImage(transparencyImage) | ||
this._image.scale(normalizedCoordinates.scale).setCoords(); | ||
this._scaleImage(normalizedCoordinates.scale); | ||
this._image.setCoords(); | ||
this._image.center(); | ||
@@ -384,2 +509,18 @@ | ||
_lockVerticalPan(imageTop) { | ||
if (!this._model.allowLetterboxing) { | ||
return; | ||
} | ||
this._image.lockMovementY = imageTop >= 0; | ||
}, | ||
_lockHorizontalPan(imageLeft) { | ||
if (!this._model.allowLetterboxing) { | ||
return; | ||
} | ||
this._image.lockMovementX = imageLeft >= 0; | ||
}, | ||
_scaleImage(scaleValue) { | ||
@@ -392,2 +533,3 @@ if (!scaleValue) { return; } | ||
}; | ||
const previousCentroid = { | ||
@@ -409,6 +551,11 @@ left: (this._image.get('left') * -1) + this._cropArea.get('left') + (this._cropArea.getWidth() / 2), | ||
this._image.set('left', this._image.get('left') + (previousCentroid.left - postCentroid.left)); | ||
this._image.set('top', this._image.get('top') + (previousCentroid.top - postCentroid.top)); | ||
const imageTop = this._image.get('top') + (previousCentroid.top - postCentroid.top); | ||
const imageLeft = this._image.get('left') + (previousCentroid.left - postCentroid.left); | ||
this._image.set('left', imageLeft); | ||
this._image.set('top', imageTop); | ||
this._image.setCoords(); | ||
this._lockVerticalPan(imageTop); | ||
this._lockHorizontalPan(imageLeft); | ||
this._checkImageBounds(); | ||
@@ -415,0 +562,0 @@ |
@@ -43,4 +43,7 @@ import extend from '@behance/nbd/util/extend'; | ||
allowTransparency: this._model.get('allowTransparency'), | ||
allowLetterboxing: this._model.get('allowLetterboxing'), | ||
previewMode: this._model.get('previewMode'), | ||
blurryImageWarningText: this._model.get('blurryImageWarningText'), | ||
backgroundHex: this._model.get('backgroundHex'), | ||
backgroundType: this._model.get('backgroundType'), | ||
}); | ||
@@ -47,0 +50,0 @@ |
import CroppingArea from 'CroppingArea'; | ||
import images from '../fixtures/images'; | ||
@@ -17,2 +18,16 @@ function createCroppingArea($el, data = {}) { | ||
}; | ||
this.loadImage = (model) => { | ||
return new Promise(resolve => { | ||
this.croppingArea = createCroppingArea(this.$el, { | ||
viewportRatio: 'static', | ||
cropWidth: 500, | ||
cropHeight: 500, | ||
image: images.flower, | ||
...model, | ||
}); | ||
this.croppingArea.on('image-loaded', resolve); | ||
}); | ||
}; | ||
}); | ||
@@ -64,3 +79,3 @@ | ||
const scale = .5; | ||
this.croppingArea._scaleView({ scale }); | ||
this.croppingArea._scaleView({ $el: this.croppingArea.$canvasContainer, scale }); | ||
@@ -74,2 +89,116 @@ this.waitAFrame().then(() => { | ||
describe('allowLetterboxing', function() { | ||
it('locks vertical pan when it is true and there is no room to pan vertically', function(done) { | ||
this.loadImage({ allowLetterboxing: true }).then(() => { | ||
spyOn(this.croppingArea._cropArea, 'get').and.callFake((arg) => { | ||
return { | ||
top: 100, | ||
}[arg]; | ||
}); | ||
this.croppingArea.trigger('scale', .5); | ||
expect(this.croppingArea._image.lockMovementY).toEqual(true); | ||
done(); | ||
}); | ||
}); | ||
it('unlocks vertical pan when it is true and there is room to pan vertically', function(done) { | ||
this.loadImage({ allowLetterboxing: true }).then(() => { | ||
spyOn(this.croppingArea._cropArea, 'get').and.callFake((arg) => { | ||
return { | ||
top: -100, | ||
}[arg]; | ||
}); | ||
this.croppingArea.trigger('scale', .5); | ||
expect(this.croppingArea._image.lockMovementY).toEqual(false); | ||
done(); | ||
}); | ||
}); | ||
it('locks horizontal pan when it is true and there is no room to pan horizontally', function(done) { | ||
this.loadImage({ allowLetterboxing: true }).then(() => { | ||
spyOn(this.croppingArea._cropArea, 'get').and.callFake((arg) => { | ||
return { | ||
left: 500, | ||
}[arg]; | ||
}); | ||
this.croppingArea.trigger('scale', .5); | ||
expect(this.croppingArea._image.lockMovementX).toEqual(true); | ||
done(); | ||
}); | ||
}); | ||
it('unlocks horizontal pan when it is true and there is room to pan horizontally', function(done) { | ||
this.loadImage({ allowLetterboxing: true }).then(() => { | ||
spyOn(this.croppingArea._cropArea, 'get').and.callFake((arg) => { | ||
return { | ||
left: -500, | ||
}[arg]; | ||
}); | ||
this.croppingArea.trigger('scale', .5); | ||
expect(this.croppingArea._image.lockMovementX).toEqual(false); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('backgroundType', function() { | ||
beforeEach(function() { | ||
this.createCroppingAreaWithBackground = (backgroundType, data = {}) => { | ||
return new Promise(resolve => { | ||
this.croppingArea = createCroppingArea(this.$el, { | ||
backgroundType, | ||
...data, | ||
}); | ||
spyOn(this.croppingArea, '_createSolidBackground').and.callThrough(); | ||
spyOn(this.croppingArea, '_createTransparencyBackground').and.callThrough(); | ||
this.croppingArea.trigger('set-image', images.flower, {}); | ||
this.croppingArea.on('background-loaded', resolve); | ||
}); | ||
}; | ||
}); | ||
it('generates image background when it is "image"', function(done) { | ||
this.createCroppingAreaWithBackground('image').then(() => { | ||
expect(this.croppingArea._backgroundCanvas).toExist(); | ||
expect(this.croppingArea._backgroundCanvas.contextContainer.filter).toEqual('blur(70px) brightness(.8)'); | ||
done(); | ||
}); | ||
}); | ||
it('generates solid color background when it is "solid"', function(done) { | ||
const backgroundHex = '#000000'; | ||
this.createCroppingAreaWithBackground('solid', { | ||
backgroundHex, | ||
canvasWidth: 100, | ||
canvasHeight: 100, | ||
}).then(() => { | ||
expect(this.croppingArea._createSolidBackground).toHaveBeenCalled(); | ||
done(); | ||
}); | ||
}); | ||
it('generates transparency background when it is not set', function(done) { | ||
this.createCroppingAreaWithBackground(null).then(() => { | ||
expect(this.croppingArea._createTransparencyBackground).toHaveBeenCalled(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#disable', function() { | ||
@@ -197,5 +326,59 @@ beforeEach(function() { | ||
this.cropWidth = 900; | ||
this.cropHeight = 1000; | ||
this.expectDimensionsToNotReduceForImage = ({ width, height }) => { | ||
return new Promise(resolve => { | ||
this.croppingArea = createCroppingArea(this.$el, { | ||
viewportRatio: 'static', | ||
cropWidth: this.cropWidth, | ||
cropHeight: this.cropHeight, | ||
allowLetterboxing: true, | ||
image: images.flower, | ||
}); | ||
spyOn(this.croppingArea, '_getImageProp').and.callFake((prop) => { | ||
return { | ||
left: 0, | ||
top: 0, | ||
width, | ||
height, | ||
}[prop]; | ||
}); | ||
this.croppingArea.on('image-loaded', () => { | ||
const crop = this.croppingArea.getCropData(); | ||
const expected = { | ||
x: Math.floor(crop.x / crop.scale), | ||
y: Math.floor(crop.y / crop.scale), | ||
width: Math.floor(crop.width / crop.scale), | ||
height: Math.floor(crop.height / crop.scale), | ||
}; | ||
const dimensions = this.croppingArea.getDimensions(); | ||
expect(dimensions).toEqual(expected); | ||
resolve(); | ||
}); | ||
}); | ||
}; | ||
this.croppingArea._image = { getScaleX: () => 0.5 }; | ||
}); | ||
it('returns non-reduces values for wide images when allowLetterboxing is true', function(done) { | ||
this.expectDimensionsToNotReduceForImage({ | ||
width: this.cropWidth * 5, | ||
height: this.cropHeight / 5, | ||
}).then(done); | ||
}); | ||
it('returns non-reduces values for tall images when allowLetterboxing is true', function(done) { | ||
this.expectDimensionsToNotReduceForImage({ | ||
width: this.cropWidth / 5, | ||
height: this.cropHeight * 5, | ||
}).then(done); | ||
}); | ||
it('returns undefined if no image is defined', function() { | ||
@@ -202,0 +385,0 @@ this.croppingArea._image = null; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
8696387
4909