add ImageViewer widget

This commit is contained in:
rolux 2014-01-05 15:26:44 +05:30
parent 1df53b7bde
commit 6550066667

View 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;
};