Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 36 additions & 38 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ tmpl.innerHTML = `
</div>
`

const startPositions = new WeakMap()
const dragStartPositions = new WeakMap()
const constructedElements = new WeakMap()
const startPositions: WeakMap<ImageCropElement, {startX: number, startY: number}> = new WeakMap()
const dragStartPositions: WeakMap<ImageCropElement, {dragStartX: number, dragStartY: number}> = new WeakMap()
const constructedElements: WeakMap<ImageCropElement, {image: HTMLImageElement, box: HTMLElement}> = new WeakMap()

function moveCropArea(event: MouseEvent | KeyboardEvent) {
const el = event.currentTarget
if (!(el instanceof ImageCropElement)) return
const {box, image} = constructedElements.get(el) || {}

let deltaX = 0
let deltaY = 0
Expand All @@ -44,12 +45,12 @@ function moveCropArea(event: MouseEvent | KeyboardEvent) {
}

if (deltaX !== 0 || deltaY !== 0) {
const x = Math.min(Math.max(0, el.box.offsetLeft + deltaX), el.image.width - el.box.offsetWidth)
const y = Math.min(Math.max(0, el.box.offsetTop + deltaY), el.image.height - el.box.offsetHeight)
el.box.style.left = `${x}px`
el.box.style.top = `${y}px`
const x = Math.min(Math.max(0, box.offsetLeft + deltaX), image.width - box.offsetWidth)
const y = Math.min(Math.max(0, box.offsetTop + deltaY), image.height - box.offsetHeight)
box.style.left = `${x}px`
box.style.top = `${y}px`

fireChangeEvent(el, {x, y, width: el.box.offsetWidth, height: el.box.offsetHeight})
fireChangeEvent(el, {x, y, width: box.offsetWidth, height: box.offsetHeight})
}

if (event instanceof MouseEvent) {
Expand All @@ -66,6 +67,7 @@ function updateCropArea(event: MouseEvent | KeyboardEvent) {

const el = target.closest('image-crop')
if (!(el instanceof ImageCropElement)) return
const {box} = constructedElements.get(el) || {}

const rect = el.getBoundingClientRect()
let deltaX, deltaY, delta
Expand All @@ -74,9 +76,9 @@ function updateCropArea(event: MouseEvent | KeyboardEvent) {
if (event.key === '-') delta = -10
if (event.key === '=') delta = +10
if (!delta) return
deltaX = el.box.offsetWidth + delta
deltaY = el.box.offsetHeight + delta
startPositions.set(el, {startX: el.box.offsetLeft, startY: el.box.offsetTop})
deltaX = box.offsetWidth + delta
deltaY = box.offsetHeight + delta
startPositions.set(el, {startX: box.offsetLeft, startY: box.offsetTop})
} else if (event instanceof MouseEvent) {
const pos = startPositions.get(el)
if (!pos) return
Expand All @@ -93,6 +95,7 @@ function startUpdate(event: MouseEvent) {

const el = currentTarget.closest('image-crop')
if (!(el instanceof ImageCropElement)) return
const {box} = constructedElements.get(el) || {}

const target = event.target
if (!(target instanceof HTMLElement)) return
Expand All @@ -104,8 +107,8 @@ function startUpdate(event: MouseEvent) {
if (['nw', 'se'].indexOf(direction) >= 0) el.classList.add('nwse')
if (['ne', 'sw'].indexOf(direction) >= 0) el.classList.add('nesw')
startPositions.set(el, {
startX: el.box.offsetLeft + (['se', 'ne'].indexOf(direction) >= 0 ? 0 : el.box.offsetWidth),
startY: el.box.offsetTop + (['se', 'sw'].indexOf(direction) >= 0 ? 0 : el.box.offsetHeight)
startX: box.offsetLeft + (['se', 'ne'].indexOf(direction) >= 0 ? 0 : box.offsetWidth),
startY: box.offsetTop + (['se', 'sw'].indexOf(direction) >= 0 ? 0 : box.offsetHeight)
})
updateCropArea(event)
} else {
Expand All @@ -118,20 +121,21 @@ function updateDimensions(target, deltaX, deltaY, reposition = true) {
let newSide = Math.max(Math.abs(deltaX), Math.abs(deltaY), 10)
const pos = startPositions.get(target)
if (!pos) return
const {box, image} = constructedElements.get(target) || {}
newSide = Math.min(
newSide,
deltaY > 0 ? target.image.height - pos.startY : pos.startY,
deltaX > 0 ? target.image.width - pos.startX : pos.startX
deltaY > 0 ? image.height - pos.startY : pos.startY,
deltaX > 0 ? image.width - pos.startX : pos.startX
)

const x = reposition ? Math.round(Math.max(0, deltaX > 0 ? pos.startX : pos.startX - newSide)) : target.box.offsetLeft
const y = reposition ? Math.round(Math.max(0, deltaY > 0 ? pos.startY : pos.startY - newSide)) : target.box.offsetTop
const x = reposition ? Math.round(Math.max(0, deltaX > 0 ? pos.startX : pos.startX - newSide)) : box.offsetLeft
const y = reposition ? Math.round(Math.max(0, deltaY > 0 ? pos.startY : pos.startY - newSide)) : box.offsetTop

target.box.style.left = `${x}px`
target.box.style.top = `${y}px`
box.style.left = `${x}px`
box.style.top = `${y}px`

target.box.style.width = `${newSide}px`
target.box.style.height = `${newSide}px`
box.style.width = `${newSide}px`
box.style.height = `${newSide}px`
fireChangeEvent(target, {x, y, width: newSide, height: newSide})
}

Expand All @@ -147,7 +151,7 @@ function imageReady(event: Event) {
}

function setInitialPosition(el) {
const image = el.image
const {image} = constructedElements.get(el) || {}
const side = Math.round(image.clientWidth > image.clientHeight ? image.clientHeight : image.clientWidth)
startPositions.set(el, {
startX: (image.clientWidth - side) / 2,
Expand All @@ -167,7 +171,8 @@ function stopUpdate(event: MouseEvent) {
}

function fireChangeEvent(target: ImageCropElement, result: {x: number, y: number, width: number, height: number}) {
const ratio = target.image.naturalWidth / target.image.width
const {image} = constructedElements.get(target) || {}
const ratio = image.naturalWidth / image.width
for (const key in result) {
const value = Math.round(result[key] * ratio)
result[key] = value
Expand All @@ -179,31 +184,23 @@ function fireChangeEvent(target: ImageCropElement, result: {x: number, y: number
}

class ImageCropElement extends HTMLElement {
image: HTMLImageElement
box: HTMLElement

connectedCallback() {
if (constructedElements.has(this)) return
constructedElements.set(this, true)

this.appendChild(document.importNode(tmpl.content, true))

const image = this.querySelector('img')
if (!(image instanceof HTMLImageElement)) return
this.image = image

const box = this.querySelector('[data-crop-box]')
if (!(box instanceof HTMLElement)) return
this.box = box
const image = this.querySelector('img')
if (!(image instanceof HTMLImageElement)) return
constructedElements.set(this, {box, image})

this.image.addEventListener('load', imageReady)
image.addEventListener('load', imageReady)
this.addEventListener('mouseleave', stopUpdate)
this.addEventListener('mouseup', stopUpdate)
this.box.addEventListener('mousedown', startUpdate)
box.addEventListener('mousedown', startUpdate)
this.addEventListener('keydown', moveCropArea)
this.addEventListener('keydown', updateCropArea)

if (this.src) this.image.src = this.src
if (this.src) image.src = this.src
}

static get observedAttributes() {
Expand Down Expand Up @@ -235,9 +232,10 @@ class ImageCropElement extends HTMLElement {
}

attributeChangedCallback(attribute: string, oldValue: string, newValue: string) {
const {image} = constructedElements.get(this) || {}
if (attribute === 'src') {
this.loaded = false
if (this.image) this.image.src = newValue
if (image) image.src = newValue
}
}
}
Expand Down
3 changes: 0 additions & 3 deletions index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

declare module '@github/image-crop-element' {
declare export default class ImageCropElement extends HTMLElement {
image: HTMLImageElement;
box: HTMLElement;

get src(): ?string;
set src(val: ?string): void;
get loaded(): boolean;
Expand Down