Created
October 22, 2019 09:00
-
-
Save cbejensen/f2f8cfdaebf27ca858175a7c67741cda to your computer and use it in GitHub Desktop.
Angular Service for Wheelzoom JS library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Service for https://github.com/jackmoore/wheelzoom | |
import { Injectable, NgZone } from '@angular/core'; | |
export interface WheelzoomSettings { | |
zoom: number; | |
maxZoom?: number; | |
initialZoom: number; | |
initialX: number; | |
initialY: number; | |
} | |
@Injectable() | |
export class WheelzoomService { | |
private _defaults: WheelzoomSettings = { | |
zoom: 0.1, | |
initialZoom: 1, | |
initialX: 0.5, | |
initialY: 0.5, | |
}; | |
private _originalProperties: { | |
backgroundImage: any; | |
backgroundRepeat: any; | |
src: any; | |
}; | |
private _img: HTMLImageElement; | |
private _settings: WheelzoomSettings; | |
private _width: number; | |
private _height: number; | |
private _bgWidth: number; | |
private _bgHeight: number; | |
private _bgPosX: number; | |
private _bgPosY: number; | |
private _previousEvent: { pageX: number; pageY: number }; | |
private _transparentSpaceFiller: string; | |
constructor(private zone: NgZone) {} | |
attach = (img: HTMLImageElement, settings: Partial<WheelzoomSettings> = {}) => { | |
if (!img || !img.nodeName || img.nodeName !== 'IMG') { | |
return; | |
} | |
this.zone.runOutsideAngular(() => { | |
this._img = img; | |
this._settings = { | |
...this._defaults, | |
...settings, | |
}; | |
this._originalProperties = { | |
backgroundImage: img.style.backgroundImage, | |
backgroundRepeat: img.style.backgroundRepeat, | |
src: img.src, | |
}; | |
img.addEventListener('wheelzoom.destroy', this.destroy); | |
if (img.complete) { | |
this._load(); | |
} | |
img.addEventListener('load', this._load); | |
}); | |
}; | |
destroy = () => { | |
this._img.removeEventListener('wheelzoom.destroy', this.destroy); | |
this._img.removeEventListener('wheelzoom.reset', this._reset); | |
this._img.removeEventListener('load', this._load); | |
this._img.removeEventListener('mouseup', this._removeDrag); | |
this._img.removeEventListener('mousemove', this._drag); | |
this._img.removeEventListener('mousedown', this._draggable); | |
this._img.removeEventListener('wheel', this._onwheel); | |
this._img.style.backgroundImage = this._originalProperties.backgroundImage; | |
this._img.style.backgroundRepeat = this._originalProperties.backgroundRepeat; | |
this._img.src = this._originalProperties.src; | |
}; | |
private _load = () => { | |
const initial = Math.max(this._settings.initialZoom, 1); | |
if (this._img.src === this._transparentSpaceFiller) return; | |
const computedStyle = window.getComputedStyle(this._img, null); | |
this._width = parseInt(computedStyle.width, 10); | |
this._height = parseInt(computedStyle.height, 10); | |
this._bgWidth = this._width * initial; | |
this._bgHeight = this._height * initial; | |
this._bgPosX = -(this._bgWidth - this._width) * this._settings.initialX; | |
this._bgPosY = -(this._bgHeight - this._height) * this._settings.initialY; | |
this._setSrcToBackground(); | |
this._img.style.backgroundSize = this._bgWidth + 'px ' + this._bgHeight + 'px'; | |
this._img.style.backgroundPosition = this._bgPosX + 'px ' + this._bgPosY + 'px'; | |
this._img.addEventListener('wheelzoom.reset', this._reset); | |
this._img.addEventListener('wheel', this._onwheel); | |
this._img.addEventListener('mousedown', this._draggable); | |
}; | |
private _setSrcToBackground = () => { | |
this._img.style.backgroundRepeat = 'no-repeat'; | |
this._img.style.backgroundImage = 'url("' + this._img.src + '")'; | |
this._transparentSpaceFiller = | |
'data:image/svg+xml;base64,' + | |
window.btoa( | |
'<svg xmlns="http://www.w3.org/2000/svg" width="' + | |
this._img.naturalWidth + | |
'" height="' + | |
this._img.naturalHeight + | |
'"></svg>' | |
); | |
this._img.src = this._transparentSpaceFiller; | |
}; | |
private _updateBgStyle = () => { | |
if (this._bgPosX > 0) { | |
this._bgPosX = 0; | |
} else if (this._bgPosX < this._width - this._bgWidth) { | |
this._bgPosX = this._width - this._bgWidth; | |
} | |
if (this._bgPosY > 0) { | |
this._bgPosY = 0; | |
} else if (this._bgPosY < this._height - this._bgHeight) { | |
this._bgPosY = this._height - this._bgHeight; | |
} | |
this._img.style.backgroundSize = this._bgWidth + 'px ' + this._bgHeight + 'px'; | |
this._img.style.backgroundPosition = this._bgPosX + 'px ' + this._bgPosY + 'px'; | |
}; | |
private _reset = () => { | |
this._bgWidth = this._width; | |
this._bgHeight = this._height; | |
this._bgPosX = this._bgPosY = 0; | |
this._updateBgStyle(); | |
}; | |
private _onwheel = (e: WheelEvent) => { | |
e.preventDefault(); | |
const deltaY = e.deltaY; | |
// As far as I know, there is no good cross-browser way to get the cursor position relative to the event target. | |
// We have to calculate the target element's position relative to the document, and subtract that from the | |
// cursor's position relative to the document. | |
const rect = this._img.getBoundingClientRect(); | |
const offsetX = e.pageX - rect.left - window.pageXOffset; | |
const offsetY = e.pageY - rect.top - window.pageYOffset; | |
// Record the offset between the bg edge and cursor: | |
const bgCursorX = offsetX - this._bgPosX; | |
const bgCursorY = offsetY - this._bgPosY; | |
// Use the previous offset to get the percent offset between the bg edge and cursor: | |
const bgRatioX = bgCursorX / this._bgWidth; | |
const bgRatioY = bgCursorY / this._bgHeight; | |
// Update the bg size: | |
if (deltaY < 0) { | |
this._bgWidth += this._bgWidth * this._settings.zoom; | |
this._bgHeight += this._bgHeight * this._settings.zoom; | |
} else { | |
this._bgWidth -= this._bgWidth * this._settings.zoom; | |
this._bgHeight -= this._bgHeight * this._settings.zoom; | |
} | |
if (this._settings.maxZoom) { | |
this._bgWidth = Math.min(this._width * this._settings.maxZoom, this._bgWidth); | |
this._bgHeight = Math.min(this._height * this._settings.maxZoom, this._bgHeight); | |
} | |
// Take the percent offset and apply it to the new size: | |
this._bgPosX = offsetX - this._bgWidth * bgRatioX; | |
this._bgPosY = offsetY - this._bgHeight * bgRatioY; | |
// Prevent zooming out beyond the starting size | |
if (this._bgWidth <= this._width || this._bgHeight <= this._height) { | |
this._reset(); | |
} else { | |
this._updateBgStyle(); | |
} | |
}; | |
private _drag = (e: DragEvent) => { | |
e.preventDefault(); | |
this._bgPosX += e.pageX - this._previousEvent.pageX; | |
this._bgPosY += e.pageY - this._previousEvent.pageY; | |
this._previousEvent = e; | |
this._updateBgStyle(); | |
}; | |
private _removeDrag = () => { | |
document.removeEventListener('mouseup', this._removeDrag); | |
document.removeEventListener('mousemove', this._drag); | |
}; | |
// Make the background draggable | |
private _draggable = (e: DragEvent) => { | |
e.preventDefault(); | |
this._previousEvent = e; | |
document.addEventListener('mousemove', this._drag); | |
document.addEventListener('mouseup', this._removeDrag); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment