diff --git a/css/twentytwenty.css b/css/twentytwenty.css
new file mode 100644
index 0000000..f8944fd
--- /dev/null
+++ b/css/twentytwenty.css
@@ -0,0 +1,216 @@
+.twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after, .twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after {
+ content: " ";
+ display: block;
+ background: white;
+ position: absolute;
+ z-index: 30;
+ -webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
+ -moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
+ box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); }
+
+.twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after {
+ width: 3px;
+ height: 9999px;
+ left: 50%;
+ margin-left: -1.5px; }
+
+.twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after {
+ width: 9999px;
+ height: 3px;
+ top: 50%;
+ margin-top: -1.5px; }
+
+.twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay {
+ position: fixed;
+ top: 50%;
+ width: 50%;
+ height: 0;
+}
+
+.twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay {
+ -webkit-transition-duration: 0.5s;
+ -moz-transition-duration: 0.5s;
+ transition-duration: 0.5s; }
+
+.twentytwenty-before-label, .twentytwenty-after-label {
+ -webkit-transition-property: opacity;
+ -moz-transition-property: opacity;
+ transition-property: opacity; }
+
+.twentytwenty-before-label:before, .twentytwenty-after-label:before {
+ color: white;
+ font-size: 13px;
+ letter-spacing: 0.1em; }
+
+.twentytwenty-before-label:before, .twentytwenty-after-label:before {
+ position: absolute;
+ background: rgba(0, 0, 0, 0.2);
+ line-height: 38px;
+ padding: 0 20px;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+ display: block !important;
+}
+
+.twentytwenty-horizontal .twentytwenty-before-label:before, .twentytwenty-horizontal .twentytwenty-after-label:before {
+ top: 50%;
+ margin-top: -19px; }
+
+.twentytwenty-vertical .twentytwenty-before-label:before, .twentytwenty-vertical .twentytwenty-after-label:before {
+ left: 50%;
+ margin-left: -45px;
+ text-align: center;
+ width: 90px; }
+
+.twentytwenty-left-arrow, .twentytwenty-right-arrow, .twentytwenty-up-arrow, .twentytwenty-down-arrow {
+ width: 0;
+ height: 0;
+ border: 6px inset transparent;
+ position: absolute; }
+
+.twentytwenty-left-arrow, .twentytwenty-right-arrow {
+ top: 50%;
+ margin-top: -6px; }
+
+.twentytwenty-up-arrow, .twentytwenty-down-arrow {
+ left: 50%;
+ margin-left: -6px; }
+
+.twentytwenty-container {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ z-index: 0;
+ overflow: scroll;
+ position: relative;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ width: 50%;
+ /* margin: 0 auto; */
+ /*height: 600px !important;*/
+}
+ .twentytwenty-container img {
+ max-width: 100%;
+ position: absolute;
+ top: 0;
+ display: block;
+ width: 100%;
+}
+ .twentytwenty-container.active .twentytwenty-overlay, .twentytwenty-container.active :hover.twentytwenty-overlay {
+ background: rgba(0, 0, 0, 0); }
+ .twentytwenty-container.active .twentytwenty-overlay .twentytwenty-before-label,
+ .twentytwenty-container.active .twentytwenty-overlay .twentytwenty-after-label, .twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-before-label,
+ .twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-after-label {
+ opacity: 0; }
+ .twentytwenty-container * {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box; }
+
+.twentytwenty-before-label {
+ /* opacity: 0; */ }
+ .twentytwenty-before-label:before {
+ content: "Before"; }
+
+.twentytwenty-after-label {
+ /* opacity: 0; */ }
+ .twentytwenty-after-label:before {
+ content: "After"; }
+
+.twentytwenty-horizontal .twentytwenty-before-label:before {
+ left: 10px; }
+
+.twentytwenty-horizontal .twentytwenty-after-label:before {
+ right: 10px; }
+
+.twentytwenty-vertical .twentytwenty-before-label:before {
+ top: 10px; }
+
+.twentytwenty-vertical .twentytwenty-after-label:before {
+ bottom: 10px; }
+
+.twentytwenty-overlay {
+ -webkit-transition-property: background;
+ -moz-transition-property: background;
+ transition-property: background;
+ background: transparent;
+ z-index: 25; }
+ .twentytwenty-overlay:hover {
+ background: rgba(0, 0, 0, 0.5); }
+ .twentytwenty-overlay:hover .twentytwenty-after-label {
+ opacity: 1; }
+ .twentytwenty-overlay:hover .twentytwenty-before-label {
+ opacity: 1; }
+
+.twentytwenty-before {
+ z-index: 20; }
+
+.twentytwenty-after {
+ z-index: 10; }
+
+.twentytwenty-handle {
+ height: 38px;
+ width: 38px;
+ position: fixed;
+ left: 50%;
+ top: 50%;
+ margin-left: -22px;
+ margin-top: -22px;
+ border: 3px solid white;
+ -webkit-border-radius: 1000px;
+ -moz-border-radius: 1000px;
+ border-radius: 1000px;
+ -webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
+ -moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
+ box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
+ z-index: 40;
+ cursor: pointer;
+ background: rgba(0, 0, 0, 0.51);
+}
+
+.twentytwenty-horizontal .twentytwenty-handle:before {
+ bottom: 50%;
+ margin-bottom: 22px;
+ -webkit-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+ -moz-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+ box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
+.twentytwenty-horizontal .twentytwenty-handle:after {
+ top: 50%;
+ margin-top: 22px;
+ -webkit-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+ -moz-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+ box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
+
+.twentytwenty-vertical .twentytwenty-handle:before {
+ left: 50%;
+ margin-left: 22px;
+ -webkit-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+ -moz-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+ box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
+.twentytwenty-vertical .twentytwenty-handle:after {
+ right: 50%;
+ margin-right: 22px;
+ -webkit-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+ -moz-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+ box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
+
+.twentytwenty-left-arrow {
+ border-right: 6px solid white;
+ left: 50%;
+ margin-left: -17px; }
+
+.twentytwenty-right-arrow {
+ border-left: 6px solid white;
+ right: 50%;
+ margin-right: -17px; }
+
+.twentytwenty-up-arrow {
+ border-bottom: 6px solid white;
+ top: 50%;
+ margin-top: -17px; }
+
+.twentytwenty-down-arrow {
+ border-top: 6px solid white;
+ bottom: 50%;
+ margin-bottom: -17px; }
diff --git a/gallery.html b/gallery.html
index 48fa176..1c7f979 100644
--- a/gallery.html
+++ b/gallery.html
@@ -1,70 +1,197 @@
-
-
-
- Screenshots - {{project}}
-
-
-
-
-
-
-
- {{#images}}
- {{#dir}}
-
-
-
/{{#resolve}}{{dir}}{{/resolve}}
-
- {{/dir}}
-
-
{{size}}px
-
-
-
-
-
{{#resolve}}{{base}}{{/resolve}}
-
-
-
-
-
-
{{#resolve}}{{compare}}{{/resolve}}
-
-
-
-
-
-
diff
-
{{#contents}}{{diff}}{{/contents}}%
-
-
- {{/images}}
-
-
-
-
+
+
+
+
+
+
+
+
+ Screenshots - {{project}}
+
+
+
+
+
+
+
+
+
+ {{#images}} {{#dir}}
+
+
+
/{{#resolve}}{{dir}}{{/resolve}}
+
+ {{/dir}}
+
+
{{size}}px
+
+
+
+
+
{{#resolve}}{{base}}{{/resolve}}
+
+
+
+
+
+
{{#resolve}}{{compare}}{{/resolve}}
+
+
+
+
+
+
diff
+
{{#contents}}{{diff}}{{/contents}}%
+
+
+ {{/images}}
+
+
+
+
+
+
diff --git a/js/jquery.event.move.js b/js/jquery.event.move.js
new file mode 100644
index 0000000..08866fa
--- /dev/null
+++ b/js/jquery.event.move.js
@@ -0,0 +1,586 @@
+// jquery.event.move
+//
+// 1.3.6
+//
+// Stephen Band
+//
+// Triggers 'movestart', 'move' and 'moveend' events after
+// mousemoves following a mousedown cross a distance threshold,
+// similar to the native 'dragstart', 'drag' and 'dragend' events.
+// Move events are throttled to animation frames. Move event objects
+// have the properties:
+//
+// pageX:
+// pageY: Page coordinates of pointer.
+// startX:
+// startY: Page coordinates of pointer at movestart.
+// distX:
+// distY: Distance the pointer has moved since movestart.
+// deltaX:
+// deltaY: Distance the finger has moved since last event.
+// velocityX:
+// velocityY: Average velocity over last few events.
+
+
+(function (module) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], module);
+ } else {
+ // Browser globals
+ module(jQuery);
+ }
+})(function(jQuery, undefined){
+
+ var // Number of pixels a pressed pointer travels before movestart
+ // event is fired.
+ threshold = 6,
+
+ add = jQuery.event.add,
+
+ remove = jQuery.event.remove,
+
+ // Just sugar, so we can have arguments in the same order as
+ // add and remove.
+ trigger = function(node, type, data) {
+ jQuery.event.trigger(type, data, node);
+ },
+
+ // Shim for requestAnimationFrame, falling back to timer. See:
+ // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ requestFrame = (function(){
+ return (
+ window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(fn, element){
+ return window.setTimeout(function(){
+ fn();
+ }, 25);
+ }
+ );
+ })(),
+
+ ignoreTags = {
+ textarea: true,
+ input: true,
+ select: true,
+ button: true
+ },
+
+ mouseevents = {
+ move: 'mousemove',
+ cancel: 'mouseup dragstart',
+ end: 'mouseup'
+ },
+
+ touchevents = {
+ move: 'touchmove',
+ cancel: 'touchend',
+ end: 'touchend'
+ };
+
+
+ // Constructors
+
+ function Timer(fn){
+ var callback = fn,
+ active = false,
+ running = false;
+
+ function trigger(time) {
+ if (active){
+ callback();
+ requestFrame(trigger);
+ running = true;
+ active = false;
+ }
+ else {
+ running = false;
+ }
+ }
+
+ this.kick = function(fn) {
+ active = true;
+ if (!running) { trigger(); }
+ };
+
+ this.end = function(fn) {
+ var cb = callback;
+
+ if (!fn) { return; }
+
+ // If the timer is not running, simply call the end callback.
+ if (!running) {
+ fn();
+ }
+ // If the timer is running, and has been kicked lately, then
+ // queue up the current callback and the end callback, otherwise
+ // just the end callback.
+ else {
+ callback = active ?
+ function(){ cb(); fn(); } :
+ fn ;
+
+ active = true;
+ }
+ };
+ }
+
+
+ // Functions
+
+ function returnTrue() {
+ return true;
+ }
+
+ function returnFalse() {
+ return false;
+ }
+
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+
+ function preventIgnoreTags(e) {
+ // Don't prevent interaction with form elements.
+ if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
+
+ e.preventDefault();
+ }
+
+ function isLeftButton(e) {
+ // Ignore mousedowns on any button other than the left (or primary)
+ // mouse button, or when a modifier key is pressed.
+ return (e.which === 1 && !e.ctrlKey && !e.altKey);
+ }
+
+ function identifiedTouch(touchList, id) {
+ var i, l;
+
+ if (touchList.identifiedTouch) {
+ return touchList.identifiedTouch(id);
+ }
+
+ // touchList.identifiedTouch() does not exist in
+ // webkit yet… we must do the search ourselves...
+
+ i = -1;
+ l = touchList.length;
+
+ while (++i < l) {
+ if (touchList[i].identifier === id) {
+ return touchList[i];
+ }
+ }
+ }
+
+ function changedTouch(e, event) {
+ var touch = identifiedTouch(e.changedTouches, event.identifier);
+
+ // This isn't the touch you're looking for.
+ if (!touch) { return; }
+
+ // Chrome Android (at least) includes touches that have not
+ // changed in e.changedTouches. That's a bit annoying. Check
+ // that this touch has changed.
+ if (touch.pageX === event.pageX && touch.pageY === event.pageY) { return; }
+
+ return touch;
+ }
+
+
+ // Handlers that decide when the first movestart is triggered
+
+ function mousedown(e){
+ var data;
+
+ if (!isLeftButton(e)) { return; }
+
+ data = {
+ target: e.target,
+ startX: e.pageX,
+ startY: e.pageY,
+ timeStamp: e.timeStamp
+ };
+
+ add(document, mouseevents.move, mousemove, data);
+ add(document, mouseevents.cancel, mouseend, data);
+ }
+
+ function mousemove(e){
+ var data = e.data;
+
+ checkThreshold(e, data, e, removeMouse);
+ }
+
+ function mouseend(e) {
+ removeMouse();
+ }
+
+ function removeMouse() {
+ remove(document, mouseevents.move, mousemove);
+ remove(document, mouseevents.cancel, mouseend);
+ }
+
+ function touchstart(e) {
+ var touch, template;
+
+ // Don't get in the way of interaction with form elements.
+ if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
+
+ touch = e.changedTouches[0];
+
+ // iOS live updates the touch objects whereas Android gives us copies.
+ // That means we can't trust the touchstart object to stay the same,
+ // so we must copy the data. This object acts as a template for
+ // movestart, move and moveend event objects.
+ template = {
+ target: touch.target,
+ startX: touch.pageX,
+ startY: touch.pageY,
+ timeStamp: e.timeStamp,
+ identifier: touch.identifier
+ };
+
+ // Use the touch identifier as a namespace, so that we can later
+ // remove handlers pertaining only to this touch.
+ add(document, touchevents.move + '.' + touch.identifier, touchmove, template);
+ add(document, touchevents.cancel + '.' + touch.identifier, touchend, template);
+ }
+
+ function touchmove(e){
+ var data = e.data,
+ touch = changedTouch(e, data);
+
+ if (!touch) { return; }
+
+ checkThreshold(e, data, touch, removeTouch);
+ }
+
+ function touchend(e) {
+ var template = e.data,
+ touch = identifiedTouch(e.changedTouches, template.identifier);
+
+ if (!touch) { return; }
+
+ removeTouch(template.identifier);
+ }
+
+ function removeTouch(identifier) {
+ remove(document, '.' + identifier, touchmove);
+ remove(document, '.' + identifier, touchend);
+ }
+
+
+ // Logic for deciding when to trigger a movestart.
+
+ function checkThreshold(e, template, touch, fn) {
+ var distX = touch.pageX - template.startX,
+ distY = touch.pageY - template.startY;
+
+ // Do nothing if the threshold has not been crossed.
+ if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; }
+
+ triggerStart(e, template, touch, distX, distY, fn);
+ }
+
+ function handled() {
+ // this._handled should return false once, and after return true.
+ this._handled = returnTrue;
+ return false;
+ }
+
+ function flagAsHandled(e) {
+ e._handled();
+ }
+
+ function triggerStart(e, template, touch, distX, distY, fn) {
+ var node = template.target,
+ touches, time;
+
+ touches = e.targetTouches;
+ time = e.timeStamp - template.timeStamp;
+
+ // Create a movestart object with some special properties that
+ // are passed only to the movestart handlers.
+ template.type = 'movestart';
+ template.distX = distX;
+ template.distY = distY;
+ template.deltaX = distX;
+ template.deltaY = distY;
+ template.pageX = touch.pageX;
+ template.pageY = touch.pageY;
+ template.velocityX = distX / time;
+ template.velocityY = distY / time;
+ template.targetTouches = touches;
+ template.finger = touches ?
+ touches.length :
+ 1 ;
+
+ // The _handled method is fired to tell the default movestart
+ // handler that one of the move events is bound.
+ template._handled = handled;
+
+ // Pass the touchmove event so it can be prevented if or when
+ // movestart is handled.
+ template._preventTouchmoveDefault = function() {
+ e.preventDefault();
+ };
+
+ // Trigger the movestart event.
+ trigger(template.target, template);
+
+ // Unbind handlers that tracked the touch or mouse up till now.
+ fn(template.identifier);
+ }
+
+
+ // Handlers that control what happens following a movestart
+
+ function activeMousemove(e) {
+ var timer = e.data.timer;
+
+ e.data.touch = e;
+ e.data.timeStamp = e.timeStamp;
+ timer.kick();
+ }
+
+ function activeMouseend(e) {
+ var event = e.data.event,
+ timer = e.data.timer;
+
+ removeActiveMouse();
+
+ endEvent(event, timer, function() {
+ // Unbind the click suppressor, waiting until after mouseup
+ // has been handled.
+ setTimeout(function(){
+ remove(event.target, 'click', returnFalse);
+ }, 0);
+ });
+ }
+
+ function removeActiveMouse(event) {
+ remove(document, mouseevents.move, activeMousemove);
+ remove(document, mouseevents.end, activeMouseend);
+ }
+
+ function activeTouchmove(e) {
+ var event = e.data.event,
+ timer = e.data.timer,
+ touch = changedTouch(e, event);
+
+ if (!touch) { return; }
+
+ // Stop the interface from gesturing
+ e.preventDefault();
+
+ event.targetTouches = e.targetTouches;
+ e.data.touch = touch;
+ e.data.timeStamp = e.timeStamp;
+ timer.kick();
+ }
+
+ function activeTouchend(e) {
+ var event = e.data.event,
+ timer = e.data.timer,
+ touch = identifiedTouch(e.changedTouches, event.identifier);
+
+ // This isn't the touch you're looking for.
+ if (!touch) { return; }
+
+ removeActiveTouch(event);
+ endEvent(event, timer);
+ }
+
+ function removeActiveTouch(event) {
+ remove(document, '.' + event.identifier, activeTouchmove);
+ remove(document, '.' + event.identifier, activeTouchend);
+ }
+
+
+ // Logic for triggering move and moveend events
+
+ function updateEvent(event, touch, timeStamp, timer) {
+ var time = timeStamp - event.timeStamp;
+
+ event.type = 'move';
+ event.distX = touch.pageX - event.startX;
+ event.distY = touch.pageY - event.startY;
+ event.deltaX = touch.pageX - event.pageX;
+ event.deltaY = touch.pageY - event.pageY;
+
+ // Average the velocity of the last few events using a decay
+ // curve to even out spurious jumps in values.
+ event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
+ event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
+ event.pageX = touch.pageX;
+ event.pageY = touch.pageY;
+ }
+
+ function endEvent(event, timer, fn) {
+ timer.end(function(){
+ event.type = 'moveend';
+
+ trigger(event.target, event);
+
+ return fn && fn();
+ });
+ }
+
+
+ // jQuery special event definition
+
+ function setup(data, namespaces, eventHandle) {
+ // Stop the node from being dragged
+ //add(this, 'dragstart.move drag.move', preventDefault);
+
+ // Prevent text selection and touch interface scrolling
+ //add(this, 'mousedown.move', preventIgnoreTags);
+
+ // Tell movestart default handler that we've handled this
+ add(this, 'movestart.move', flagAsHandled);
+
+ // Don't bind to the DOM. For speed.
+ return true;
+ }
+
+ function teardown(namespaces) {
+ remove(this, 'dragstart drag', preventDefault);
+ remove(this, 'mousedown touchstart', preventIgnoreTags);
+ remove(this, 'movestart', flagAsHandled);
+
+ // Don't bind to the DOM. For speed.
+ return true;
+ }
+
+ function addMethod(handleObj) {
+ // We're not interested in preventing defaults for handlers that
+ // come from internal move or moveend bindings
+ if (handleObj.namespace === "move" || handleObj.namespace === "moveend") {
+ return;
+ }
+
+ // Stop the node from being dragged
+ add(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid, preventDefault, undefined, handleObj.selector);
+
+ // Prevent text selection and touch interface scrolling
+ add(this, 'mousedown.' + handleObj.guid, preventIgnoreTags, undefined, handleObj.selector);
+ }
+
+ function removeMethod(handleObj) {
+ if (handleObj.namespace === "move" || handleObj.namespace === "moveend") {
+ return;
+ }
+
+ remove(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid);
+ remove(this, 'mousedown.' + handleObj.guid);
+ }
+
+ jQuery.event.special.movestart = {
+ setup: setup,
+ teardown: teardown,
+ add: addMethod,
+ remove: removeMethod,
+
+ _default: function(e) {
+ var event, data;
+
+ // If no move events were bound to any ancestors of this
+ // target, high tail it out of here.
+ if (!e._handled()) { return; }
+
+ function update(time) {
+ updateEvent(event, data.touch, data.timeStamp);
+ trigger(e.target, event);
+ }
+
+ event = {
+ target: e.target,
+ startX: e.startX,
+ startY: e.startY,
+ pageX: e.pageX,
+ pageY: e.pageY,
+ distX: e.distX,
+ distY: e.distY,
+ deltaX: e.deltaX,
+ deltaY: e.deltaY,
+ velocityX: e.velocityX,
+ velocityY: e.velocityY,
+ timeStamp: e.timeStamp,
+ identifier: e.identifier,
+ targetTouches: e.targetTouches,
+ finger: e.finger
+ };
+
+ data = {
+ event: event,
+ timer: new Timer(update),
+ touch: undefined,
+ timeStamp: undefined
+ };
+
+ if (e.identifier === undefined) {
+ // We're dealing with a mouse
+ // Stop clicks from propagating during a move
+ add(e.target, 'click', returnFalse);
+ add(document, mouseevents.move, activeMousemove, data);
+ add(document, mouseevents.end, activeMouseend, data);
+ }
+ else {
+ // We're dealing with a touch. Stop touchmove doing
+ // anything defaulty.
+ e._preventTouchmoveDefault();
+ add(document, touchevents.move + '.' + e.identifier, activeTouchmove, data);
+ add(document, touchevents.end + '.' + e.identifier, activeTouchend, data);
+ }
+ }
+ };
+
+ jQuery.event.special.move = {
+ setup: function() {
+ // Bind a noop to movestart. Why? It's the movestart
+ // setup that decides whether other move events are fired.
+ add(this, 'movestart.move', jQuery.noop);
+ },
+
+ teardown: function() {
+ remove(this, 'movestart.move', jQuery.noop);
+ }
+ };
+
+ jQuery.event.special.moveend = {
+ setup: function() {
+ // Bind a noop to movestart. Why? It's the movestart
+ // setup that decides whether other move events are fired.
+ add(this, 'movestart.moveend', jQuery.noop);
+ },
+
+ teardown: function() {
+ remove(this, 'movestart.moveend', jQuery.noop);
+ }
+ };
+
+ add(document, 'mousedown.move', mousedown);
+ add(document, 'touchstart.move', touchstart);
+
+ // Make jQuery copy touch event properties over to the jQuery event
+ // object, if they are not already listed. But only do the ones we
+ // really need. IE7/8 do not have Array#indexOf(), but nor do they
+ // have touch events, so let's assume we can ignore them.
+ if (typeof Array.prototype.indexOf === 'function') {
+ (function(jQuery, undefined){
+ var props = ["changedTouches", "targetTouches"],
+ l = props.length;
+
+ while (l--) {
+ if (jQuery.event.props.indexOf(props[l]) === -1) {
+ jQuery.event.props.push(props[l]);
+ }
+ }
+ })(jQuery);
+ };
+});
diff --git a/js/jquery.twentytwenty.js b/js/jquery.twentytwenty.js
new file mode 100644
index 0000000..c9eccf9
--- /dev/null
+++ b/js/jquery.twentytwenty.js
@@ -0,0 +1,103 @@
+(function($){
+
+ $.fn.twentytwenty = function(options) {
+ var options = $.extend({default_offset_pct: 0.5, orientation: 'horizontal'}, options);
+ return this.each(function() {
+
+ var sliderPct = options.default_offset_pct;
+ var container = $(this);
+ var sliderOrientation = options.orientation;
+ var beforeDirection = (sliderOrientation === 'vertical') ? 'down' : 'left';
+ var afterDirection = (sliderOrientation === 'vertical') ? 'up' : 'right';
+
+
+ container.wrap("");
+ container.append("");
+ var beforeImg = container.find("img:first");
+ var afterImg = container.find("img:last");
+ container.append("");
+ var slider = container.find(".twentytwenty-handle");
+ slider.append("");
+ slider.append("");
+ container.addClass("twentytwenty-container");
+ beforeImg.addClass("twentytwenty-before");
+ afterImg.addClass("twentytwenty-after");
+
+ var overlay = container.find(".twentytwenty-overlay");
+ overlay.append("");
+ overlay.append("");
+
+ var calcOffset = function(dimensionPct) {
+ var w = beforeImg.width();
+ var h = beforeImg.height();
+ return {
+ w: w+"px",
+ h: h+"px",
+ cw: (dimensionPct*w)+"px",
+ ch: (dimensionPct*h)+"px"
+ };
+ };
+
+ var adjustContainer = function(offset) {
+ if (sliderOrientation === 'vertical') {
+ beforeImg.css("clip", "rect(0,"+offset.w+","+offset.ch+",0)");
+ }
+ else {
+ beforeImg.css("clip", "rect(0,"+offset.cw+","+offset.h+",0)");
+ }
+ container.css("height", offset.h);
+ };
+
+ var adjustSlider = function(pct) {
+ var offset = calcOffset(pct);
+ slider.css((sliderOrientation==="vertical") ? "top" : "left", (sliderOrientation==="vertical") ? offset.ch : offset.cw);
+ adjustContainer(offset);
+ }
+
+ $(window).on("resize.twentytwenty", function(e) {
+ adjustSlider(sliderPct);
+ });
+
+ var offsetX = 0;
+ var imgWidth = 0;
+
+ slider.on("movestart", function(e) {
+ if (((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) && sliderOrientation !== 'vertical') {
+ e.preventDefault();
+ }
+ else if (((e.distX < e.distY && e.distX < -e.distY) || (e.distX > e.distY && e.distX > -e.distY)) && sliderOrientation === 'vertical') {
+ e.preventDefault();
+ }
+ container.addClass("active");
+ offsetX = container.offset().left;
+ offsetY = container.offset().top;
+ imgWidth = beforeImg.width();
+ imgHeight = beforeImg.height();
+ });
+
+ slider.on("moveend", function(e) {
+ container.removeClass("active");
+ });
+
+ slider.on("move", function(e) {
+ if (container.hasClass("active")) {
+ sliderPct = (sliderOrientation === 'vertical') ? (e.pageY-offsetY)/imgHeight : (e.pageX-offsetX)/imgWidth;
+ if (sliderPct < 0) {
+ sliderPct = 0;
+ }
+ if (sliderPct > 1) {
+ sliderPct = 1;
+ }
+ adjustSlider(sliderPct);
+ }
+ });
+
+ container.find("img").on("mousedown", function(event) {
+ event.preventDefault();
+ });
+
+ $(window).trigger("resize.twentytwenty");
+ });
+ };
+
+})(jQuery);
diff --git a/lib/gallery.js b/lib/gallery.js
index e1681de..ae1ecbd 100644
--- a/lib/gallery.js
+++ b/lib/gallery.js
@@ -5,10 +5,14 @@ var path = require('path');
var mustache = require('mustache');
var helpers = require('./helpers');
var log = require('./logger');
+var mkdirp = require('mkdirp');
module.exports.generate = function(dirs, compareList, outputDir, project, cb) {
compareList = compareList.sort(helpers.sortByProp('sort'));
var template = path.join(__dirname, '/../gallery.html');
+ var twentytwentyJs = path.join(__dirname, '/../js/jquery.twentytwenty.js');
+ var jqueryEventMoveJs = path.join(__dirname, '/../js/jquery.event.move.js');
+ var twentytwentyCSS = path.join(__dirname, '/../css/twentytwenty.css');
var view = {
'images' : compareList,
'dirs': dirs.sort(),
@@ -27,6 +31,31 @@ module.exports.generate = function(dirs, compareList, outputDir, project, cb) {
};
}
};
+ // Add twentytwenty plugin to be used for images comparison
+ mkdirp(path.join(outputDir,'/js/'), function(err) {
+ fs.readFile(twentytwentyJs, function (err, data) {
+ if (err) { throw err; }
+ fs.writeFile(path.join(outputDir,'/js/jquery.twentytwenty.js'), data, function(err) {
+ if(err) {
+ log.error(err);
+ }
+ });
+ });
+ fs.readFile(jqueryEventMoveJs, function (err, data) {
+ if (err) { throw err; }
+ fs.writeFile(path.join(outputDir,'/js/jquery.event.move.js'), data, function(err) {
+ if(err) { log.error(err); }
+ });
+ });
+ });
+ mkdirp(path.join(outputDir,'/css/'), function(err) {
+ fs.readFile(twentytwentyCSS, function (err, data) {
+ if (err) { throw err; }
+ fs.writeFile(path.join(outputDir,'/css/twentytwenty.css'), data, function(err) {
+ if(err) { log.error(err); }
+ });
+ });
+ });
fs.readFile(template, function (err, data) {
if (err) { throw err; }
@@ -40,4 +69,4 @@ module.exports.generate = function(dirs, compareList, outputDir, project, cb) {
}
});
});
-};
\ No newline at end of file
+};
diff --git a/package.json b/package.json
index e6cc198..4cc03c6 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,8 @@
"progress": "^1.1.8",
"rimraf": "~2.2.6",
"w3counter": "^1.0.3",
- "webshot": "^0.15.3"
+ "webshot": "^0.15.3",
+ "mkdirp": "^0.3.5"
},
"devDependencies": {
"jshint": "^2.5.11",