Ce script ne devrait pas être installé directement. C'est une librairie créée pour d'autres scripts. Elle doit être inclus avec la commande // @require https://update.greasyfork.org/scripts/420842/927891/vanilla-js-wheel-zoom.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = factory())
: typeof define === 'function' && define.amd
? define(factory)
: ((global =
typeof globalThis !== 'undefined' ? globalThis : global || self),
(global.WZoom = factory()));
})(this, function () {
'use strict';
/**
* Get element position (with support old browsers)
* @param {Element} element
* @returns {{top: number, left: number}}
*/
function getElementPosition(element) {
var box = element.getBoundingClientRect();
var _document = document,
body = _document.body,
documentElement = _document.documentElement;
var scrollTop =
window.pageYOffset || documentElement.scrollTop || body.scrollTop;
var scrollLeft =
window.pageXOffset || documentElement.scrollLeft || body.scrollLeft;
var clientTop = documentElement.clientTop || body.clientTop || 0;
var clientLeft = documentElement.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return {
top: top,
left: left,
};
}
/**
* Universal alternative to Object.assign()
* @param {Object} destination
* @param {Object} source
* @returns {Object}
*/
function extendObject(destination, source) {
if (destination && source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
destination[key] = source[key];
}
}
}
return destination;
}
/**
* @param target
* @param type
* @param listener
* @param options
*/
function on(target, type, listener) {
var options =
arguments.length > 3 && arguments[3] !== undefined
? arguments[3]
: false;
target.addEventListener(type, listener, options);
}
/**
* @param target
* @param type
* @param listener
* @param options
*/
function off(target, type, listener) {
var options =
arguments.length > 3 && arguments[3] !== undefined
? arguments[3]
: false;
target.removeEventListener(type, listener, options);
}
function isTouch() {
return (
'ontouchstart' in window ||
navigator.MaxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
);
}
function eventClientX(event) {
return event.type === 'wheel' ||
event.type === 'mousedown' ||
event.type === 'mousemove' ||
event.type === 'mouseup'
? event.clientX
: event.changedTouches[0].clientX;
}
function eventClientY(event) {
return event.type === 'wheel' ||
event.type === 'mousedown' ||
event.type === 'mousemove' ||
event.type === 'mouseup'
? event.clientY
: event.changedTouches[0].clientY;
}
/**
* @class DragScrollable
* @param {Object} windowObject
* @param {Object} contentObject
* @param {Object} options
* @constructor
*/
function DragScrollable(windowObject, contentObject) {
var options =
arguments.length > 2 && arguments[2] !== undefined
? arguments[2]
: {};
this._dropHandler = this._dropHandler.bind(this);
this._grabHandler = this._grabHandler.bind(this);
this._moveHandler = this._moveHandler.bind(this);
this.options = extendObject(
{
// smooth extinction moving element after set loose
smoothExtinction: false,
// callback triggered when grabbing an element
onGrab: null,
// callback triggered when moving an element
onMove: null,
// callback triggered when dropping an element
onDrop: null,
},
options
); // check if we're using a touch screen
this.isTouch = isTouch(); // switch to touch events if using a touch screen
this.events = this.isTouch
? {
grab: 'touchstart',
move: 'touchmove',
drop: 'touchend',
}
: {
grab: 'mousedown',
move: 'mousemove',
drop: 'mouseup',
}; // for the touch screen we set the parameter forcibly
this.events.options = this.isTouch
? {
passive: false,
}
: false;
this.window = windowObject;
this.content = contentObject;
on(
this.content.$element,
this.events.grab,
this._grabHandler,
this.events.options
);
}
DragScrollable.prototype = {
constructor: DragScrollable,
window: null,
content: null,
isTouch: false,
isGrab: false,
events: null,
moveTimer: null,
options: {},
coordinates: null,
speed: null,
_grabHandler: function _grabHandler(event) {
// if touch started (only one finger) or pressed left mouse button
if (
(this.isTouch && event.touches.length === 1) ||
event.buttons === 1
) {
event.preventDefault();
this.isGrab = true;
this.coordinates = {
left: eventClientX(event),
top: eventClientY(event),
};
this.speed = {
x: 0,
y: 0,
};
on(
document,
this.events.drop,
this._dropHandler,
this.events.options
);
on(
document,
this.events.move,
this._moveHandler,
this.events.options
);
if (typeof this.options.onGrab === 'function') {
this.options.onGrab();
}
}
},
_dropHandler: function _dropHandler(event) {
event.preventDefault();
this.isGrab = false; // if (this.options.smoothExtinction) {
// _moveExtinction.call(this, 'scrollLeft', numberExtinction(this.speed.x));
// _moveExtinction.call(this, 'scrollTop', numberExtinction(this.speed.y));
// }
off(document, this.events.drop, this._dropHandler);
off(document, this.events.move, this._moveHandler);
if (typeof this.options.onDrop === 'function') {
this.options.onDrop();
}
},
_moveHandler: function _moveHandler(event) {
if (this.isTouch && event.touches.length > 1) return false;
event.preventDefault();
var window = this.window,
content = this.content,
speed = this.speed,
coordinates = this.coordinates,
options = this.options; // speed of change of the coordinate of the mouse cursor along the X/Y axis
speed.x = eventClientX(event) - coordinates.left;
speed.y = eventClientY(event) - coordinates.top;
clearTimeout(this.moveTimer); // reset speed data if cursor stops
this.moveTimer = setTimeout(function () {
speed.x = 0;
speed.y = 0;
}, 50);
var contentNewLeft = content.currentLeft + speed.x;
var contentNewTop = content.currentTop + speed.y;
var maxAvailableLeft =
(content.currentWidth - window.originalWidth) / 2 +
content.correctX;
var maxAvailableTop =
(content.currentHeight - window.originalHeight) / 2 +
content.correctY; // if we do not go beyond the permissible boundaries of the window
if (Math.abs(contentNewLeft) <= maxAvailableLeft)
content.currentLeft = contentNewLeft; // if we do not go beyond the permissible boundaries of the window
if (Math.abs(contentNewTop) <= maxAvailableTop)
content.currentTop = contentNewTop;
_transform(content.$element, {
left: content.currentLeft,
top: content.currentTop,
scale: content.currentScale,
});
coordinates.left = eventClientX(event);
coordinates.top = eventClientY(event);
if (typeof options.onMove === 'function') {
options.onMove();
}
},
destroy: function destroy() {
off(
this.content.$element,
this.events.grab,
this._grabHandler,
this.events.options
);
for (var key in this) {
if (this.hasOwnProperty(key)) {
this[key] = null;
}
}
},
};
function _transform($element, _ref) {
var left = _ref.left,
top = _ref.top,
scale = _ref.scale;
$element.style.transform = 'translate3d('
.concat(left, 'px, ')
.concat(top, 'px, 0px) scale(')
.concat(scale, ')');
} // function _moveExtinction(field, speedArray) {
/**
* @class WZoom
* @param {string|HTMLElement} selectorOrHTMLElement
* @param {Object} options
* @constructor
*/
function WZoom(selectorOrHTMLElement) {
var options =
arguments.length > 1 && arguments[1] !== undefined
? arguments[1]
: {};
this._init = this._init.bind(this);
this._prepare = this._prepare.bind(this);
this._computeNewScale = this._computeNewScale.bind(this);
this._computeNewPosition = this._computeNewPosition.bind(this);
this._transform = this._transform.bind(this);
this._wheelHandler = _wheelHandler.bind(this);
this._downHandler = _downHandler.bind(this);
this._upHandler = _upHandler.bind(this);
this._zoomTwoFingers_TouchmoveHandler = _zoomTwoFingers_TouchmoveHandler.bind(
this
);
this._zoomTwoFingers_TouchendHandler = _zoomTwoFingers_TouchendHandler.bind(
this
);
/********************/
/********************/
this.content = {};
this.window = {};
this.isTouch = false;
this.events = null;
this.direction = 1;
this.options = null;
this.dragScrollable = null; // processing of the event "max / min zoom" begin only if there was really just a click
// so as not to interfere with the DragScrollable module
this.clickExpired = true;
/********************/
/********************/
var defaults = {
// type content: `image` - only one image, `html` - any HTML content
type: 'image',
// for type `image` computed auto (if width set null), for type `html` need set real html content width, else computed auto
width: null,
// for type `image` computed auto (if height set null), for type `html` need set real html content height, else computed auto
height: null,
// drag scrollable content
dragScrollable: true,
// options for the DragScrollable module
dragScrollableOptions: {},
// minimum allowed proportion of scale
minScale: null,
// maximum allowed proportion of scale
maxScale: 1,
// content resizing speed
speed: 50,
// zoom to maximum (minimum) size on click
zoomOnClick: true,
// if is true, then when the source image changes, the plugin will automatically restart init function (used with type = image)
// attention: if false, it will work correctly only if the images are of the same size
watchImageChange: true,
};
if (typeof selectorOrHTMLElement === 'string') {
this.content.$element = document.querySelector(
selectorOrHTMLElement
);
} else if (selectorOrHTMLElement instanceof HTMLElement) {
this.content.$element = selectorOrHTMLElement;
} else {
throw 'WZoom: `selectorOrHTMLElement` must be selector or HTMLElement, and not '.concat(
{}.toString.call(selectorOrHTMLElement)
);
} // check if we're using a touch screen
this.isTouch = isTouch(); // switch to touch events if using a touch screen
this.events = this.isTouch
? {
down: 'touchstart',
up: 'touchend',
}
: {
down: 'mousedown',
up: 'mouseup',
}; // if using touch screen tells the browser that the default action will not be undone
this.events.options = this.isTouch
? {
passive: true,
}
: false;
if (this.content.$element) {
this.options = extendObject(defaults, options);
if (
this.options.minScale &&
this.options.minScale >= this.options.maxScale
) {
this.options.minScale = null;
} // for window take just the parent
this.window.$element = this.content.$element.parentNode;
if (this.options.type === 'image') {
var initAlreadyDone = false; // if the `image` has already been loaded
if (this.content.$element.complete) {
this._init();
initAlreadyDone = true;
}
if (
!initAlreadyDone ||
this.options.watchImageChange === true
) {
// even if the `image` has already been loaded (for "hotswap" of src support)
on(
this.content.$element,
'load',
this._init, // if watchImageChange == false listen add only until the first call
this.options.watchImageChange
? false
: {
once: true,
}
);
}
} else {
this._init();
}
}
}
WZoom.prototype = {
constructor: WZoom,
_init: function _init() {
this._prepare(); // support for zoom and pinch on touch screen devices
if (this.isTouch) {
this.fingersHypot = null;
this.zoomPinchWasDetected = false;
on(
this.content.$element,
'touchmove',
this._zoomTwoFingers_TouchmoveHandler
);
on(
this.content.$element,
'touchend',
this._zoomTwoFingers_TouchendHandler
);
}
if (this.options.dragScrollable === true) {
// this can happen if the src of this.content.$element (when type = image) is changed and repeat event load at image
if (this.dragScrollable) {
this.dragScrollable.destroy();
}
this.dragScrollable = new DragScrollable(
this.window,
this.content,
this.options.dragScrollableOptions
);
}
on(this.content.$element, 'wheel', this._wheelHandler);
if (this.options.zoomOnClick) {
on(
this.content.$element,
this.events.down,
this._downHandler,
this.events.options
);
on(
this.content.$element,
this.events.up,
this._upHandler,
this.events.options
);
}
},
_prepare: function _prepare() {
var windowPosition = getElementPosition(this.window.$element); // original window sizes and position
this.window.originalWidth = this.window.$element.offsetWidth;
this.window.originalHeight = this.window.$element.offsetHeight;
this.window.positionLeft = windowPosition.left;
this.window.positionTop = windowPosition.top; // original content sizes
if (this.options.type === 'image') {
this.content.originalWidth =
this.options.width || this.content.$element.naturalWidth;
this.content.originalHeight =
this.options.height || this.content.$element.naturalHeight;
} else {
this.content.originalWidth =
this.options.width || this.content.$element.offsetWidth;
this.content.originalHeight =
this.options.height || this.content.$element.offsetHeight;
} // minScale && maxScale
this.content.minScale =
this.options.minScale ||
Math.min(
this.window.originalWidth / this.content.originalWidth,
this.window.originalHeight / this.content.originalHeight
);
this.content.maxScale = this.options.maxScale; // current content sizes and transform data
this.content.currentWidth =
this.content.originalWidth * this.content.minScale;
this.content.currentHeight =
this.content.originalHeight * this.content.minScale;
this.content.currentLeft = 0;
this.content.currentTop = 0;
this.content.currentScale = this.content.minScale; // calculate indent-left and indent-top to of content from window borders
this.content.correctX = Math.max(
0,
(this.window.originalWidth - this.content.currentWidth) / 2
);
this.content.correctY = Math.max(
0,
(this.window.originalHeight - this.content.currentHeight) / 2
);
this.content.$element.style.transform = 'translate3d(0px, 0px, 0px) scale('.concat(
this.content.minScale,
')'
);
if (typeof this.options.prepare === 'function') {
this.options.prepare();
}
},
_computeNewScale: function _computeNewScale(delta) {
this.direction = delta < 0 ? 1 : -1;
var _this$content = this.content,
minScale = _this$content.minScale,
maxScale = _this$content.maxScale,
currentScale = _this$content.currentScale;
var contentNewScale =
currentScale + this.direction / this.options.speed;
if (contentNewScale < minScale) {
this.direction = 1;
} else if (contentNewScale > maxScale) {
this.direction = -1;
}
return contentNewScale < minScale
? minScale
: contentNewScale > maxScale
? maxScale
: contentNewScale;
},
_computeNewPosition: function _computeNewPosition(
contentNewScale,
_ref
) {
var x = _ref.x,
y = _ref.y;
var window = this.window,
content = this.content;
var contentNewWidth = content.originalWidth * contentNewScale;
var contentNewHeight = content.originalHeight * contentNewScale;
var _document = document,
body = _document.body,
documentElement = _document.documentElement;
var scrollLeft =
window.pageXOffset ||
documentElement.scrollLeft ||
body.scrollLeft;
var scrollTop =
window.pageYOffset ||
documentElement.scrollTop ||
body.scrollTop; // calculate the parameters along the X axis
var leftWindowShiftX = x + scrollLeft - window.positionLeft;
var centerWindowShiftX =
window.originalWidth / 2 - leftWindowShiftX;
var centerContentShiftX = centerWindowShiftX + content.currentLeft;
var contentNewLeft =
centerContentShiftX * (contentNewWidth / content.currentWidth) -
centerContentShiftX +
content.currentLeft; // check that the content does not go beyond the X axis
if (
this.direction === -1 &&
(contentNewWidth - window.originalWidth) / 2 +
content.correctX <
Math.abs(contentNewLeft)
) {
var positive = contentNewLeft < 0 ? -1 : 1;
contentNewLeft =
((contentNewWidth - window.originalWidth) / 2 +
content.correctX) *
positive;
} // calculate the parameters along the Y axis
var topWindowShiftY = y + scrollTop - window.positionTop;
var centerWindowShiftY =
window.originalHeight / 2 - topWindowShiftY;
var centerContentShiftY = centerWindowShiftY + content.currentTop;
var contentNewTop =
centerContentShiftY *
(contentNewHeight / content.currentHeight) -
centerContentShiftY +
content.currentTop; // check that the content does not go beyond the Y axis
if (
this.direction === -1 &&
(contentNewHeight - window.originalHeight) / 2 +
content.correctY <
Math.abs(contentNewTop)
) {
var _positive = contentNewTop < 0 ? -1 : 1;
contentNewTop =
((contentNewHeight - window.originalHeight) / 2 +
content.correctY) *
_positive;
}
if (contentNewScale === this.content.minScale) {
contentNewLeft = contentNewTop = 0;
}
var response = {
currentLeft: content.currentLeft,
newLeft: contentNewLeft,
currentTop: content.currentTop,
newTop: contentNewTop,
currentScale: content.currentScale,
newScale: contentNewScale,
};
content.currentWidth = contentNewWidth;
content.currentHeight = contentNewHeight;
content.currentLeft = contentNewLeft;
content.currentTop = contentNewTop;
content.currentScale = contentNewScale;
return response;
},
_transform: function _transform(_ref2) {
_ref2.currentLeft;
var newLeft = _ref2.newLeft;
_ref2.currentTop;
var newTop = _ref2.newTop;
_ref2.currentScale;
var newScale = _ref2.newScale;
this.content.$element.style.transform = 'translate3d('
.concat(newLeft, 'px, ')
.concat(newTop, 'px, 0px) scale(')
.concat(newScale, ')');
if (typeof this.options.rescale === 'function') {
this.options.rescale();
}
},
_zoom: function _zoom(direction) {
var windowPosition = getElementPosition(this.window.$element);
var window = this.window;
var _document2 = document,
body = _document2.body,
documentElement = _document2.documentElement;
var scrollLeft =
window.pageXOffset ||
documentElement.scrollLeft ||
body.scrollLeft;
var scrollTop =
window.pageYOffset ||
documentElement.scrollTop ||
body.scrollTop;
this._transform(
this._computeNewPosition(this._computeNewScale(direction), {
x:
windowPosition.left +
this.window.originalWidth / 2 -
scrollLeft,
y:
windowPosition.top +
this.window.originalHeight / 2 -
scrollTop,
})
);
},
prepare: function prepare() {
this._prepare();
},
zoomUp: function zoomUp() {
this._zoom(-1);
},
zoomDown: function zoomDown() {
this._zoom(1);
},
destroy: function destroy() {
this.content.$element.style.transform = '';
if (this.options.type === 'image') {
off(this.content.$element, 'load', this._init);
}
if (this.isTouch) {
off(
this.content.$element,
'touchmove',
this._zoomTwoFingers_TouchmoveHandler
);
off(
this.content.$element,
'touchend',
this._zoomTwoFingers_TouchendHandler
);
}
off(this.window.$element, 'wheel', this._wheelHandler);
if (this.options.zoomOnClick) {
off(
this.window.$element,
this.events.down,
this._downHandler,
this.events.options
);
off(
this.window.$element,
this.events.up,
this._upHandler,
this.events.options
);
}
if (this.dragScrollable) {
this.dragScrollable.destroy();
}
for (var key in this) {
if (this.hasOwnProperty(key)) {
this[key] = null;
}
}
},
};
function _wheelHandler(event) {
event.preventDefault();
this._transform(
this._computeNewPosition(this._computeNewScale(event.deltaY), {
x: eventClientX(event),
y: eventClientY(event),
})
);
}
function _downHandler(event) {
var _this = this;
if (
(this.isTouch && event.touches.length === 1) ||
event.buttons === 1
) {
this.clickExpired = false;
setTimeout(function () {
return (_this.clickExpired = true);
}, 150);
}
}
function _upHandler(event) {
if (!this.clickExpired) {
this._transform(
this._computeNewPosition(
this.direction === 1
? this.content.maxScale
: this.content.minScale,
{
x: eventClientX(event),
y: eventClientY(event),
}
)
);
this.direction *= -1;
}
}
function _zoomTwoFingers_TouchmoveHandler(event) {
// detect two fingers
if (event.targetTouches.length === 2) {
var pageX1 = event.targetTouches[0].clientX;
var pageY1 = event.targetTouches[0].clientY;
var pageX2 = event.targetTouches[1].clientX;
var pageY2 = event.targetTouches[1].clientY; // Math.hypot() analog
var fingersHypotNew = Math.round(
Math.sqrt(
Math.pow(Math.abs(pageX1 - pageX2), 2) +
Math.pow(Math.abs(pageY1 - pageY2), 2)
)
);
var direction = 0;
if (fingersHypotNew > this.fingersHypot + 5) direction = -1;
if (fingersHypotNew < this.fingersHypot - 5) direction = 1;
if (direction !== 0) {
console.log(
'move',
direction,
this.fingersHypot,
fingersHypotNew
);
if (this.fingersHypot !== null || direction === 1) {
var eventEmulator = new Event('wheel'); // sized direction
eventEmulator.deltaY = direction; // middle position between fingers
eventEmulator.clientX =
Math.min(pageX1, pageX2) +
Math.abs(pageX1 - pageX2) / 2;
eventEmulator.clientY =
Math.min(pageY1, pageY2) +
Math.abs(pageY1 - pageY2) / 2;
this._wheelHandler(eventEmulator);
}
this.fingersHypot = fingersHypotNew;
this.zoomPinchWasDetected = true;
}
}
}
function _zoomTwoFingers_TouchendHandler() {
if (this.zoomPinchWasDetected) {
this.fingersHypot = null;
this.zoomPinchWasDetected = false;
console.log('end', this.fingersHypot);
}
}
/**
* Create WZoom instance
* @param {string|HTMLElement} selectorOrHTMLElement
* @param {Object} [options]
* @returns {WZoom}
*/
WZoom.create = function (selectorOrHTMLElement, options) {
return new WZoom(selectorOrHTMLElement, options);
};
return WZoom;
});