add ImageViewer widget
This commit is contained in:
parent
1df53b7bde
commit
6550066667
1 changed files with 571 additions and 0 deletions
571
source/Ox.UI/js/Image/ImageViewer.js
Normal file
571
source/Ox.UI/js/Image/ImageViewer.js
Normal file
|
@ -0,0 +1,571 @@
|
|||
'use strict';
|
||||
|
||||
/*@
|
||||
Ox.ImageViewer <f> Image Viewer
|
||||
options <o> Options
|
||||
center <[n]|s|'auto'> Center ([x, y] or 'auto')
|
||||
elasticity <n|0> Number of pixels to scroll/zoom beyond min/max
|
||||
height <n|384> Viewer height in px
|
||||
imageHeight <n|0> Image height in px
|
||||
imagePreviewURL <s|''> URL of smaller preview image
|
||||
imageURL <s|''> Image URL
|
||||
imageWidth <n|0> Image width in px
|
||||
maxZoom <n|16> Maximum zoom (minimum zoom is 'fit')
|
||||
overviewSize <n|128> Size of overview image in px
|
||||
width <n|512> Viewer width in px
|
||||
zoom <n|s|'fit'> Zoom (number or 'fit' or 'fill')
|
||||
self <o> Shared private variable
|
||||
([options[, self]]) -> <o:OxElement> Image Viewer
|
||||
center <!> Center changed
|
||||
center <[n]|s> Center
|
||||
zoom <!> Zoom changed
|
||||
zoom <n|s> Zoom
|
||||
@*/
|
||||
|
||||
Ox.ImageViewer = function(options, self) {
|
||||
|
||||
self = self || {};
|
||||
var that = Ox.Element({}, self)
|
||||
.defaults({
|
||||
center: 'auto',
|
||||
elasticity: 0,
|
||||
height: 384,
|
||||
imageHeight: 0,
|
||||
imagePreviewURL: '',
|
||||
imageURL: '',
|
||||
imageWidth: 0,
|
||||
maxZoom: 16,
|
||||
overviewSize: 128,
|
||||
width: 512,
|
||||
zoom: 'fit'
|
||||
})
|
||||
.options(options || {})
|
||||
.update({
|
||||
center: function() {
|
||||
setCenterAndZoom(true, true);
|
||||
},
|
||||
// allow for setting height and width at the same time
|
||||
height: updateSize,
|
||||
width: updateSize,
|
||||
zoom: function() {
|
||||
setCenterAndZoom(true, true);
|
||||
}
|
||||
})
|
||||
.addClass('OxImageViewer OxGrid')
|
||||
.on({
|
||||
mousedown: function() {
|
||||
that.gainFocus();
|
||||
},
|
||||
mouseenter: function() {
|
||||
showInterface();
|
||||
},
|
||||
mouseleave: function() {
|
||||
hideInterface();
|
||||
},
|
||||
mousemove: function() {
|
||||
showInterface();
|
||||
hideInterface();
|
||||
}
|
||||
})
|
||||
.bindEvent({
|
||||
doubleclick: onDoubleclick,
|
||||
dragstart: onDragstart,
|
||||
drag: onDrag,
|
||||
dragend: onDragend,
|
||||
key_0: function() {
|
||||
that.options({zoom: 1});
|
||||
},
|
||||
key_1: function() {
|
||||
that.options({center: 'auto', zoom: 'fit'});
|
||||
},
|
||||
key_2: function() {
|
||||
that.options({center: 'auto', zoom: 'fill'});
|
||||
},
|
||||
key_down: function() {
|
||||
that.options({
|
||||
center: [
|
||||
self.center[0],
|
||||
self.center[1] + self.options.height / 2 / self.zoom
|
||||
]
|
||||
});
|
||||
},
|
||||
key_equal: function() {
|
||||
that.options({zoom: self.zoom * 2});
|
||||
},
|
||||
key_left: function() {
|
||||
that.options({
|
||||
center: [
|
||||
self.center[0] - self.options.width / 2 / self.zoom,
|
||||
self.center[1]
|
||||
]
|
||||
});
|
||||
},
|
||||
key_minus: function() {
|
||||
that.options({zoom: self.zoom / 2});
|
||||
},
|
||||
key_right: function() {
|
||||
that.options({
|
||||
center: [
|
||||
self.center[0] + self.options.width / 2 / self.zoom,
|
||||
self.center[1]
|
||||
]
|
||||
});
|
||||
},
|
||||
key_up: function() {
|
||||
that.options({
|
||||
center: [
|
||||
self.center[0],
|
||||
self.center[1] - self.options.height / 2 / self.zoom
|
||||
]
|
||||
});
|
||||
},
|
||||
mousewheel: onMousewheel,
|
||||
singleclick: onSingleclick
|
||||
});
|
||||
|
||||
self.imageRatio = self.options.imageWidth / self.options.imageHeight;
|
||||
self.overviewHeight = Math.round(
|
||||
self.options.overviewSize / (self.imageRatio > 1 ? self.imageRatio : 1)
|
||||
);
|
||||
self.overviewWidth = Math.round(
|
||||
self.options.overviewSize * (self.imageRatio > 1 ? 1 : self.imageRatio)
|
||||
);
|
||||
self.overviewZoom = self.overviewWidth / self.options.imageWidth;
|
||||
|
||||
self.$image = Ox.Element('<img>')
|
||||
.addClass('OxImage')
|
||||
.attr({src: self.options.imagePreviewURL})
|
||||
//.css(getImageCSS())
|
||||
.appendTo(that);
|
||||
|
||||
Ox.$('<img>')
|
||||
.one({
|
||||
load: function() {
|
||||
self.$image.attr({src: self.options.imageURL});
|
||||
}
|
||||
})
|
||||
.attr({src: self.options.imageURL});
|
||||
|
||||
self.$scaleButton = Ox.ButtonGroup({
|
||||
buttons: [
|
||||
{
|
||||
id: 'fit',
|
||||
title: 'fit',
|
||||
tooltip: Ox._('Zoom to Fit') + ' <span class="OxBright">[1]</span>'
|
||||
},
|
||||
{
|
||||
id: 'fill',
|
||||
title: 'fill',
|
||||
tooltip: Ox._('Zoom to Fill') + ' <span class="OxBright">[2]</span>'
|
||||
}
|
||||
],
|
||||
style: 'overlay',
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxInterface OxScaleButton')
|
||||
.on({
|
||||
mouseenter: function() {
|
||||
self.mouseIsInInterface = true;
|
||||
},
|
||||
mouseleave: function() {
|
||||
self.mouseIsInInterface = false;
|
||||
}
|
||||
})
|
||||
.bindEvent({
|
||||
click: function(data) {
|
||||
that.options({center: 'auto', zoom: data.id});
|
||||
}
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
self.$zoomButton = Ox.ButtonGroup({
|
||||
buttons: [
|
||||
{
|
||||
id: 'out',
|
||||
title: 'remove',
|
||||
tooltip: Ox._('Zoom Out') + ' <span class="OxBright">[-]</span>'
|
||||
},
|
||||
{
|
||||
id: 'original',
|
||||
title: 'equal',
|
||||
tooltip: Ox._('Original Size') + ' <span class="OxBright">[0]</span>'
|
||||
},
|
||||
{
|
||||
id: 'in',
|
||||
title: 'add',
|
||||
tooltip: Ox._('Zoom In') + ' <span class="OxBright">[=]</span>'
|
||||
}
|
||||
],
|
||||
style: 'overlay',
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxInterface OxZoomButton')
|
||||
.on({
|
||||
mouseenter: function() {
|
||||
self.mouseIsInInterface = true;
|
||||
},
|
||||
mouseleave: function() {
|
||||
self.mouseIsInInterface = false;
|
||||
}
|
||||
})
|
||||
.bindEvent({
|
||||
click: function(data) {
|
||||
if (data.id == 'out') {
|
||||
that.options({zoom: self.zoom / 2});
|
||||
} else if (data.id == 'original') {
|
||||
that.options({zoom: 1});
|
||||
} else {
|
||||
that.options({zoom: self.zoom * 2});
|
||||
}
|
||||
}
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
self.$overview = Ox.Element()
|
||||
.addClass('OxInterface OxImageOverview')
|
||||
.css({
|
||||
height: self.overviewHeight + 'px',
|
||||
width: self.overviewWidth + 'px'
|
||||
})
|
||||
.on({
|
||||
mouseenter: function() {
|
||||
self.mouseIsInInterface = true;
|
||||
},
|
||||
mouseleave: function() {
|
||||
self.mouseIsInInterface = false;
|
||||
}
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
self.$overviewImage = Ox.Element('<img>')
|
||||
.attr({src: self.options.imagePreviewURL})
|
||||
.css({
|
||||
height: self.overviewHeight + 'px',
|
||||
width: self.overviewWidth + 'px'
|
||||
})
|
||||
.appendTo(self.$overview);
|
||||
|
||||
self.$overlay = Ox.Element()
|
||||
.addClass('OxImageOverlay')
|
||||
.appendTo(self.$overview);
|
||||
|
||||
self.$area = {};
|
||||
['bottom', 'center', 'left', 'right', 'top'].forEach(function(area) {
|
||||
self.$area[area] = Ox.Element()
|
||||
.addClass('OxImageOverlayArea')
|
||||
.attr({id: 'OxImageOverlay' + Ox.toTitleCase(area)})
|
||||
.css(getAreaCSS(area))
|
||||
.appendTo(self.$overlay);
|
||||
});
|
||||
|
||||
setSize();
|
||||
setCenterAndZoom();
|
||||
|
||||
function getAreaCSS(area) {
|
||||
return area == 'bottom' ? {
|
||||
height: self.overviewHeight + 'px'
|
||||
} : area == 'center' ? {
|
||||
left: self.overviewWidth + 'px',
|
||||
top: self.overviewHeight + 'px',
|
||||
right: self.overviewWidth + 'px',
|
||||
bottom: self.overviewHeight + 'px'
|
||||
} : area == 'left' ? {
|
||||
top: self.overviewHeight + 'px',
|
||||
bottom: self.overviewHeight + 'px',
|
||||
width: self.overviewWidth + 'px'
|
||||
} : area == 'right' ? {
|
||||
top: self.overviewHeight + 'px',
|
||||
bottom: self.overviewHeight + 'px',
|
||||
width: self.overviewWidth + 'px'
|
||||
} : {
|
||||
height: self.overviewHeight + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
function getCenter(e) {
|
||||
var $target = $(e.target), center, offset, offsetX, offsetY;
|
||||
if ($target.is('.OxImage')) {
|
||||
center = [e.offsetX / self.zoom, e.offsetY / self.zoom];
|
||||
} else if ($target.is('.OxImageOverlayArea')) {
|
||||
offset = that.offset();
|
||||
offsetX = e.clientX - offset.left - self.options.width
|
||||
+ self.overviewWidth + 6;
|
||||
offsetY = e.clientY - offset.top - self.options.height
|
||||
+ self.overviewHeight + 6;
|
||||
center = [offsetX / self.overviewZoom, offsetY / self.overviewZoom];
|
||||
}
|
||||
return center;
|
||||
}
|
||||
|
||||
function getImageCSS() {
|
||||
return {
|
||||
left: Math.round(self.options.width / 2 - self.center[0] * self.zoom) + 'px',
|
||||
top: Math.round(self.options.height / 2 - self.center[1] * self.zoom) + 'px',
|
||||
width: Math.round(self.options.imageWidth * self.zoom) + 'px',
|
||||
height: Math.round(self.options.imageHeight * self.zoom) + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
function getOverlayCSS() {
|
||||
var centerLeft = self.center[0] / self.options.imageWidth * self.overviewWidth,
|
||||
centerTop = self.center[1] / self.options.imageHeight * self.overviewHeight,
|
||||
centerWidth = self.options.width / self.zoom * self.overviewZoom + 4,
|
||||
centerHeight = self.options.height / self.zoom * self.overviewZoom + 4;
|
||||
return {
|
||||
left: Math.round(centerLeft - centerWidth / 2 - self.overviewWidth) + 'px',
|
||||
top: Math.round(centerTop - centerHeight / 2 - self.overviewHeight) + 'px',
|
||||
width: Math.round(2 * self.overviewWidth + centerWidth) + 'px',
|
||||
height: Math.round(2 * self.overviewHeight + centerHeight) + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
function getZoomCenter(e, factor) {
|
||||
var center = getCenter(e),
|
||||
delta = [
|
||||
center[0] - self.center[0],
|
||||
center[1] - self.center[1]
|
||||
];
|
||||
if (factor == 0.5) {
|
||||
factor = -1;
|
||||
}
|
||||
return [
|
||||
self.center[0] + delta[0] / factor,
|
||||
self.center[1] + delta[1] / factor
|
||||
];
|
||||
}
|
||||
|
||||
function hideInterface() {
|
||||
clearTimeout(self.interfaceTimeout);
|
||||
self.interfaceTimeout = setTimeout(function() {
|
||||
if (!self.mouseIsInInterface) {
|
||||
self.interfaceIsVisible = false;
|
||||
self.$scaleButton.animate({opacity: 0}, 250);
|
||||
self.$zoomButton.animate({opacity: 0}, 250);
|
||||
self.$overview.animate({opacity: 0}, 250);
|
||||
}
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function limitCenter(elastic) {
|
||||
var center, imageHeight, imageWidth, maxCenter, minCenter;
|
||||
if (self.options.zoom == 'fill') {
|
||||
imageWidth = self.imageIsWider
|
||||
? self.options.height * self.imageRatio
|
||||
: self.options.width;
|
||||
imageHeight = self.imageIsWider
|
||||
? self.options.height
|
||||
: self.options.width / self.imageRatio;
|
||||
} else if (self.options.zoom == 'fit') {
|
||||
imageWidth = self.imageIsWider
|
||||
? self.options.width
|
||||
: self.options.height * self.imageRatio;
|
||||
imageHeight = self.imageIsWider
|
||||
? self.options.width / self.imageRatio
|
||||
: self.options.height;
|
||||
} else {
|
||||
imageWidth = self.options.imageWidth * self.options.zoom;
|
||||
imageHeight = self.options.imageHeight * self.options.zoom;
|
||||
}
|
||||
minCenter = [
|
||||
imageWidth > self.options.width
|
||||
? self.options.width / 2 / self.zoom
|
||||
: self.options.imageWidth / 2,
|
||||
imageHeight > self.options.height
|
||||
? self.options.height / 2 / self.zoom
|
||||
: self.options.imageHeight / 2
|
||||
].map(function(value) {
|
||||
return elastic ? value - self.options.elasticity / self.zoom : value;
|
||||
});
|
||||
maxCenter = [
|
||||
self.options.imageWidth - minCenter[0],
|
||||
self.options.imageHeight - minCenter[1]
|
||||
];
|
||||
center = self.options.center == 'auto' ? [
|
||||
self.options.imageWidth / 2,
|
||||
self.options.imageHeight / 2
|
||||
] : [
|
||||
Ox.limit(self.options.center[0], minCenter[0], maxCenter[0]),
|
||||
Ox.limit(self.options.center[1], minCenter[1], maxCenter[1])
|
||||
];
|
||||
if (Ox.isArray(self.options.center)) {
|
||||
self.options.center = center;
|
||||
}
|
||||
return center;
|
||||
}
|
||||
|
||||
function limitZoom(elastic) {
|
||||
var imageSize = self.imageIsWider ? self.options.imageWidth : self.options.imageHeight,
|
||||
minZoom = elastic
|
||||
? (self.fitZoom * imageSize - 2 * self.options.elasticity) / imageSize
|
||||
: self.fitZoom,
|
||||
maxZoom = elastic
|
||||
? (self.maxZoom * imageSize + 2 * self.options.elasticity) / imageSize
|
||||
: self.maxZoom,
|
||||
zoom = self.options.zoom == 'fill' ? self.fillZoom
|
||||
: self.options.zoom == 'fit' ? self.fitZoom
|
||||
: Ox.limit(self.options.zoom, minZoom, maxZoom);
|
||||
if (Ox.isNumber(self.options.zoom)) {
|
||||
self.options.zoom = zoom;
|
||||
}
|
||||
return zoom;
|
||||
}
|
||||
|
||||
function onDoubleclick(e) {
|
||||
var $target = $(e.target), factor = e.shiftKey ? 0.5 : 2;
|
||||
if ((
|
||||
$target.is('.OxImage') || $target.is('.OxImageOverlayArea')
|
||||
) && (
|
||||
(!e.shiftKey && self.zoom < self.maxZoom)
|
||||
|| (e.shiftKey && self.zoom > self.fitZoom)
|
||||
)) {
|
||||
that.options({
|
||||
center: getZoomCenter(e, factor),
|
||||
zoom: self.zoom * factor
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onDragstart(e) {
|
||||
var $target = $(e.target);
|
||||
if ($target.is('.OxImage') || $target.is('#OxImageOverlayCenter')) {
|
||||
self.drag = {
|
||||
center: self.center,
|
||||
zoom: $target.is('.OxImage') ? self.zoom : -self.overviewZoom
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function onDrag(e) {
|
||||
if (self.drag) {
|
||||
self.options.center = [
|
||||
self.drag.center[0] - e.clientDX / self.drag.zoom,
|
||||
self.drag.center[1] - e.clientDY / self.drag.zoom
|
||||
];
|
||||
setCenterAndZoom(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
function onDragend() {
|
||||
if (self.drag) {
|
||||
self.drag = null;
|
||||
setCenterAndZoom(true);
|
||||
}
|
||||
}
|
||||
|
||||
function onMousewheel(e) {
|
||||
var $target = $(e.target),
|
||||
factor = e.deltaY < 0 ? 2 : 0.5;
|
||||
Ox.print('MW', e.deltaY);
|
||||
if (e.deltaX == 0 && Math.abs(e.deltaY) > 10 && !self.mousewheelTimeout) {
|
||||
if ($target.is('.OxImage') || $target.is('.OxImageOverlayArea')) {
|
||||
self.options.center = getZoomCenter(e, factor);
|
||||
self.options.zoom = self.zoom * factor;
|
||||
setCenterAndZoom(true, true);
|
||||
self.mousewheelTimeout = setTimeout(function() {
|
||||
self.mousewheelTimeout = null;
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onSingleclick(e) {
|
||||
var $target = $(e.target), offset, offsetX, offsetY;
|
||||
if ($target.is('.OxImage') || $target.is('.OxImageOverlayArea')) {
|
||||
that.options({center: getCenter(e)});
|
||||
}
|
||||
}
|
||||
|
||||
function setCenterAndZoom(animate, elastic) {
|
||||
self.zoom = limitZoom(elastic);
|
||||
self.center = limitCenter(elastic);
|
||||
if (animate) {
|
||||
self.$image.stop().animate(getImageCSS(), 250, function() {
|
||||
var center = limitCenter(),
|
||||
zoom = limitZoom(),
|
||||
setCenter = center[0] != self.center[0]
|
||||
|| center[1] != self.center[1],
|
||||
setZoom = zoom != self.zoom;
|
||||
if (setCenter || setZoom) {
|
||||
self.options.center = center;
|
||||
self.options.zoom = zoom;
|
||||
setCenterAndZoom();
|
||||
}
|
||||
that.triggerEvent({
|
||||
center: {center: self.options.center},
|
||||
zoom: {zoom: self.options.zoom}
|
||||
});
|
||||
});
|
||||
self.$overlay.stop().animate(getOverlayCSS(), 250);
|
||||
} else {
|
||||
self.$image.css(getImageCSS());
|
||||
self.$overlay.css(getOverlayCSS());
|
||||
}
|
||||
updateButtons();
|
||||
showInterface();
|
||||
hideInterface();
|
||||
}
|
||||
|
||||
function setSize() {
|
||||
self.viewerRatio = self.options.width / self.options.height;
|
||||
self.imageIsWider = self.imageRatio > self.viewerRatio;
|
||||
self.fillZoom = (
|
||||
self.imageIsWider
|
||||
? self.options.height * self.imageRatio
|
||||
: self.options.width
|
||||
) / self.options.imageWidth;
|
||||
self.fitZoom = (
|
||||
self.imageIsWider
|
||||
? self.options.width
|
||||
: self.options.height * self.imageRatio
|
||||
) / self.options.imageWidth;
|
||||
self.maxZoom = Math.max(self.fillZoom, self.options.maxZoom);
|
||||
that.css({
|
||||
width: self.options.width + 'px',
|
||||
height: self.options.height + 'px'
|
||||
});
|
||||
setCenterAndZoom();
|
||||
}
|
||||
|
||||
function showInterface() {
|
||||
clearTimeout(self.interfaceTimeout);
|
||||
if (!self.interfaceIsVisible) {
|
||||
self.interfaceIsVisible = true;
|
||||
self.$scaleButton.animate({opacity: 1}, 250);
|
||||
self.$zoomButton.animate({opacity: 1}, 250);
|
||||
self.$overview.animate({opacity: 1}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
self.$scaleButton[
|
||||
self.zoom == self.fitZoom ? 'disableButton' : 'enableButton'
|
||||
]('fit');
|
||||
self.$scaleButton[
|
||||
self.zoom == self.fillZoom
|
||||
&& self.center[0] == self.options.imageWidth / 2
|
||||
&& self.center[1] == self.options.imageHeight / 2
|
||||
? 'disableButton' : 'enableButton'
|
||||
]('fill');
|
||||
self.$zoomButton[
|
||||
self.zoom == self.fitZoom ? 'disableButton' : 'enableButton'
|
||||
]('out');
|
||||
self.$zoomButton[
|
||||
self.zoom == 1 ? 'disableButton' : 'enableButton'
|
||||
]('original');
|
||||
self.$zoomButton[
|
||||
self.zoom == self.maxZoom ? 'disableButton' : 'enableButton'
|
||||
]('in');
|
||||
}
|
||||
|
||||
function updateSize() {
|
||||
if (!self.updateTimeout) {
|
||||
self.updateTimeout = setTimeout(function() {
|
||||
self.updateTimeout = null;
|
||||
setSize();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
Loading…
Reference in a new issue