zero-g
Version:
A utility library for efficiently adding panning and zooming capabilities to any DOM element. Comes with out-of-the-box TypeScript typings!
246 lines (245 loc) • 10.5 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
var util_1 = require("./util");
var Orientation;
(function (Orientation) {
Orientation[Orientation["Landscape"] = 0] = "Landscape";
Orientation[Orientation["Portrait"] = 1] = "Portrait";
Orientation[Orientation["Square"] = 2] = "Square";
})(Orientation || (Orientation = {}));
var defaultPannerOptions = {
changeCursorOnPan: true,
disabled: false,
refitOnResize: true,
onScaleChange: function (currentScale) { return undefined; },
onPanEnd: function (panEvent, instance) { return undefined; },
onPanMove: function (panEvent, instance) { return undefined; },
onPanStart: function (panEvent, instance) { return undefined; },
};
var allowedPannerOptionKeys = Object.keys(defaultPannerOptions);
var ZeroGInstance = (function () {
function ZeroGInstance(element, options, controlledByDockingProcedure) {
var _this = this;
if (options === void 0) { options = defaultPannerOptions; }
if (controlledByDockingProcedure === void 0) { controlledByDockingProcedure = false; }
this.element = element;
this.options = options;
this.controlledByDockingProcedure = controlledByDockingProcedure;
this.lastX = null;
this.lastY = null;
this.zoom = null;
this.mousedown = false;
this.hasLoadHandler = false;
this.preventDrag = function (e) {
e.preventDefault();
};
this.handleMouseDown = function (e) {
_this.mousedown = true;
if (_this.options.changeCursorOnPan)
_this.swapMouseCursor();
if (_this.options.onPanStart)
_this.options.onPanStart({ lastX: null, lastY: null, x: e.pageX, y: e.pageY }, _this);
};
this.handleMouseUp = function (e) {
if (_this.options.onPanEnd)
_this.options.onPanEnd({ lastX: _this.lastX, lastY: _this.lastY, x: e.pageX, y: e.pageY }, _this);
_this.clearLast();
_this.mousedown = false;
if (_this.options.changeCursorOnPan)
_this.swapMouseCursor();
};
this.handleMouseMove = function (e) {
if (_this.mousedown) {
if (_this.options.onPanMove)
_this.options.onPanMove({ lastX: _this.lastX, lastY: _this.lastY, x: e.pageX, y: e.pageY }, _this);
if (!_this.controlledByDockingProcedure)
_this.doPan(e.pageX, e.pageY);
}
};
this.handleWindowResize = function () {
if (_this.windowResizeTimeout)
_this.windowResizeTimeout = clearTimeout(_this.windowResizeTimeout);
_this.windowResizeTimeout = setTimeout(function () { return _this.zoomFit(); }, 1);
};
this.handleInitialLoad = function () {
_this.computeNaturalDimensions();
_this.fit();
};
this.options = __assign(__assign({}, defaultPannerOptions), options);
this.init();
}
Object.defineProperty(ZeroGInstance.prototype, "currentScale", {
get: function () {
return Math.min(this.element.clientHeight / this.naturalHeight, this.element.clientWidth / this.naturalWidth);
},
enumerable: true,
configurable: true
});
ZeroGInstance.prototype.bindHandlers = function () {
if (this.element instanceof HTMLImageElement || this.element instanceof HTMLVideoElement) {
this.hasLoadHandler = true;
this.element.addEventListener('load', this.handleInitialLoad);
}
this.element.addEventListener('mousedown', this.handleMouseDown);
this.element.addEventListener('dragstart', this.preventDrag);
document.addEventListener('mouseup', this.handleMouseUp);
document.addEventListener('mousemove', this.handleMouseMove);
if (this.options.refitOnResize)
window.addEventListener('resize', this.handleWindowResize);
};
ZeroGInstance.prototype.unbindHandlers = function () {
if (this.hasLoadHandler) {
this.hasLoadHandler = false;
this.element.removeEventListener('load', this.handleInitialLoad);
}
this.element.removeEventListener('mousedown', this.handleMouseDown);
document.removeEventListener('mouseup', this.handleMouseUp);
document.removeEventListener('mousemove', this.handleMouseMove);
};
ZeroGInstance.prototype.computeNaturalDimensions = function () {
if (this.element instanceof HTMLImageElement) {
this.naturalHeight = this.element.naturalHeight;
this.naturalWidth = this.element.naturalWidth;
}
else if (this.element instanceof HTMLVideoElement) {
this.naturalHeight = this.element.videoHeight;
this.naturalWidth = this.element.videoWidth;
}
else {
this.naturalHeight = this.element.clientHeight;
this.naturalWidth = this.element.clientWidth;
}
};
ZeroGInstance.prototype.init = function () {
this.parent = this.element.parentElement;
this.computeNaturalDimensions();
if (this.naturalWidth > this.naturalHeight)
this.orientation = Orientation.Landscape;
else if (this.naturalHeight > this.naturalWidth)
this.orientation = Orientation.Portrait;
else
this.orientation = Orientation.Square;
this.element.style.position = 'absolute';
this.element.style.willChange = 'top, left, width, height';
if (this.options.changeCursorOnPan)
this.swapMouseCursor();
this.bindHandlers();
this.queueInitialFit();
};
ZeroGInstance.prototype.fitLandscape = function () {
this.element.style.width = '100%';
this.element.style.height = 'auto';
this.element.style.left = util_1.toPx(0);
this.element.style.top = util_1.toPx((this.parent.clientHeight - this.element.clientHeight) / 2);
};
ZeroGInstance.prototype.fitPortrait = function () {
this.element.style.width = 'auto';
this.element.style.height = '100%';
this.element.style.left = util_1.toPx((this.parent.clientWidth - this.element.clientWidth) / 2);
this.element.style.top = util_1.toPx(0);
};
ZeroGInstance.prototype.adjustIfOverflown = function () {
if (this.element.clientHeight > this.parent.clientHeight)
return this.fitPortrait();
if (this.element.clientWidth > this.parent.clientWidth)
return this.fitLandscape();
};
ZeroGInstance.prototype.swapMouseCursor = function () {
if (this.mousedown)
this.element.style.cursor = 'grabbing';
else
this.element.style.cursor = 'grab';
};
ZeroGInstance.prototype.doPan = function (pageX, pageY, lastX, lastY) {
if (lastX === void 0) { lastX = this.lastX; }
if (lastY === void 0) { lastY = this.lastY; }
var deltaX = lastX !== null ? pageX - lastX : 0;
var deltaY = lastY !== null ? pageY - lastY : 0;
this.element.style.top = util_1.toPx(this.element.offsetTop + deltaY);
this.element.style.left = util_1.toPx(this.element.offsetLeft + deltaX);
this.lastX = pageX;
this.lastY = pageY;
};
ZeroGInstance.prototype.fit = function () {
switch (this.orientation) {
case Orientation.Landscape:
this.fitLandscape();
break;
case Orientation.Portrait:
this.fitPortrait();
break;
case Orientation.Square:
this.fitLandscape();
break;
default:
break;
}
this.adjustIfOverflown();
if (this.options.onScaleChange)
this.options.onScaleChange(this.currentScale);
};
ZeroGInstance.prototype.queueInitialFit = function () {
if (this.element instanceof HTMLImageElement || this.element instanceof HTMLVideoElement) {
this.element.addEventListener('load', this.handleInitialLoad);
}
};
ZeroGInstance.prototype.destroy = function () {
this.unbindHandlers();
};
ZeroGInstance.prototype.controlledPan = function (panEvent) {
this.pan(panEvent.x, panEvent.y, panEvent.lastX, panEvent.lastY);
};
ZeroGInstance.prototype.set = function (prop, val, reinit) {
if (reinit === void 0) { reinit = false; }
if (allowedPannerOptionKeys.indexOf(prop) > -1) {
this.options[prop] = val;
if (reinit) {
this.unbindHandlers();
this.init();
}
}
};
ZeroGInstance.prototype.zoomFit = function () {
this.zoom = null;
this.fit();
};
ZeroGInstance.prototype.zoomInOut = function (level) {
this.zoom = level;
var newHeight = this.naturalHeight * this.zoom;
var newWidth = this.naturalWidth * this.zoom;
this.element.style.height = util_1.toPx(newHeight);
this.element.style.width = util_1.toPx(newWidth);
if (this.options.onScaleChange)
this.options.onScaleChange(this.currentScale);
};
ZeroGInstance.prototype.pan = function (x, y, lastX, lastY) {
this.doPan(x, y, lastX, lastY);
};
ZeroGInstance.prototype.clearLast = function () {
this.lastX = this.lastY = null;
};
return ZeroGInstance;
}());
exports.ZeroGInstance = ZeroGInstance;
function createZeroG(element, options) {
if (options === void 0) { options = defaultPannerOptions; }
if (!element)
throw new Error('Unable to initialize zero-g because no DOM element was provided');
if (!element.parentElement)
throw new Error('Unable to initialize zero-g because DOM element provided has no parent');
var instance = new ZeroGInstance(element, options);
instance.zoomFit();
return instance;
}
exports.default = createZeroG;