369 lines
10 KiB
JavaScript
369 lines
10 KiB
JavaScript
/*! flip - v1.1.2 - 2016-10-20
|
|
* https://github.com/nnattawat/flip
|
|
* Copyright (c) 2016 Nattawat Nonsung; Licensed MIT */
|
|
(function( $ ) {
|
|
/*
|
|
* Private attributes and method
|
|
*/
|
|
|
|
// Function from David Walsh: http://davidwalsh.name/css-animation-callback licensed with http://opensource.org/licenses/MIT
|
|
var whichTransitionEvent = function() {
|
|
var t, el = document.createElement("fakeelement"),
|
|
transitions = {
|
|
"transition" : "transitionend",
|
|
"OTransition" : "oTransitionEnd",
|
|
"MozTransition" : "transitionend",
|
|
"WebkitTransition": "webkitTransitionEnd"
|
|
};
|
|
|
|
for (t in transitions) {
|
|
if (el.style[t] !== undefined) {
|
|
return transitions[t];
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Model declaration
|
|
*/
|
|
var Flip = function($el, options, callback) {
|
|
// Define default setting
|
|
this.setting = {
|
|
axis: "y",
|
|
reverse: false,
|
|
trigger: "click",
|
|
speed: 500,
|
|
forceHeight: false,
|
|
forceWidth: false,
|
|
autoSize: true,
|
|
front: '.front',
|
|
back: '.back'
|
|
};
|
|
|
|
this.setting = $.extend(this.setting, options);
|
|
|
|
if (typeof options.axis === 'string' && (options.axis.toLowerCase() === 'x' || options.axis.toLowerCase() === 'y')) {
|
|
this.setting.axis = options.axis.toLowerCase();
|
|
}
|
|
|
|
if (typeof options.reverse === "boolean") {
|
|
this.setting.reverse = options.reverse;
|
|
}
|
|
|
|
if (typeof options.trigger === 'string') {
|
|
this.setting.trigger = options.trigger.toLowerCase();
|
|
}
|
|
|
|
var speed = parseInt(options.speed);
|
|
if (!isNaN(speed)) {
|
|
this.setting.speed = speed;
|
|
}
|
|
|
|
if (typeof options.forceHeight === "boolean") {
|
|
this.setting.forceHeight = options.forceHeight;
|
|
}
|
|
|
|
if (typeof options.forceWidth === "boolean") {
|
|
this.setting.forceWidth = options.forceWidth;
|
|
}
|
|
|
|
if (typeof options.autoSize === "boolean") {
|
|
this.setting.autoSize = options.autoSize;
|
|
}
|
|
|
|
if (typeof options.front === 'string' || options.front instanceof $) {
|
|
this.setting.front = options.front;
|
|
}
|
|
|
|
if (typeof options.back === 'string' || options.back instanceof $) {
|
|
this.setting.back = options.back;
|
|
}
|
|
|
|
// Other attributes
|
|
this.element = $el;
|
|
this.frontElement = this.getFrontElement();
|
|
this.backElement = this.getBackElement();
|
|
this.isFlipped = false;
|
|
|
|
this.init(callback);
|
|
};
|
|
|
|
/*
|
|
* Public methods
|
|
*/
|
|
$.extend(Flip.prototype, {
|
|
|
|
flipDone: function(callback) {
|
|
var self = this;
|
|
// Providing a nicely wrapped up callback because transform is essentially async
|
|
self.element.one(whichTransitionEvent(), function() {
|
|
self.element.trigger('flip:done');
|
|
if (typeof callback === 'function') {
|
|
callback.call(self.element);
|
|
}
|
|
});
|
|
},
|
|
|
|
flip: function(callback) {
|
|
if (this.isFlipped) {
|
|
return;
|
|
}
|
|
|
|
this.isFlipped = true;
|
|
|
|
var rotateAxis = "rotate" + this.setting.axis;
|
|
this.frontElement.css({
|
|
transform: rotateAxis + (this.setting.reverse ? "(-180deg)" : "(180deg)"),
|
|
"z-index": "0"
|
|
});
|
|
|
|
this.backElement.css({
|
|
transform: rotateAxis + "(0deg)",
|
|
"z-index": "1"
|
|
});
|
|
this.flipDone(callback);
|
|
},
|
|
|
|
unflip: function(callback) {
|
|
if (!this.isFlipped) {
|
|
return;
|
|
}
|
|
|
|
this.isFlipped = false;
|
|
|
|
var rotateAxis = "rotate" + this.setting.axis;
|
|
this.frontElement.css({
|
|
transform: rotateAxis + "(0deg)",
|
|
"z-index": "1"
|
|
});
|
|
|
|
this.backElement.css({
|
|
transform: rotateAxis + (this.setting.reverse ? "(180deg)" : "(-180deg)"),
|
|
"z-index": "0"
|
|
});
|
|
this.flipDone(callback);
|
|
},
|
|
|
|
getFrontElement: function() {
|
|
if (this.setting.front instanceof $) {
|
|
return this.setting.front;
|
|
} else {
|
|
return this.element.find(this.setting.front);
|
|
}
|
|
},
|
|
|
|
getBackElement: function() {
|
|
if (this.setting.back instanceof $) {
|
|
return this.setting.back;
|
|
} else {
|
|
return this.element.find(this.setting.back);
|
|
}
|
|
},
|
|
|
|
init: function(callback) {
|
|
var self = this;
|
|
|
|
var faces = self.frontElement.add(self.backElement);
|
|
var rotateAxis = "rotate" + self.setting.axis;
|
|
var perspective = self.element["outer" + (rotateAxis === "rotatex" ? "Height" : "Width")]() * 2;
|
|
var elementCss = {
|
|
'perspective': perspective,
|
|
'position': 'relative'
|
|
};
|
|
var backElementCss = {
|
|
"transform": rotateAxis + "(" + (self.setting.reverse ? "180deg" : "-180deg") + ")",
|
|
"z-index": "0",
|
|
"position": "relative"
|
|
};
|
|
var faceElementCss = {
|
|
"backface-visibility": "hidden",
|
|
"transform-style": "preserve-3d",
|
|
"position": "absolute",
|
|
"z-index": "1"
|
|
};
|
|
|
|
if (self.setting.forceHeight) {
|
|
faces.outerHeight(self.element.height());
|
|
} else if (self.setting.autoSize) {
|
|
faceElementCss.height = '100%';
|
|
}
|
|
|
|
if (self.setting.forceWidth) {
|
|
faces.outerWidth(self.element.width());
|
|
} else if (self.setting.autoSize) {
|
|
faceElementCss.width = '100%';
|
|
}
|
|
|
|
// Back face always visible on Chrome #39
|
|
if ((window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window) {
|
|
//Blink Engine, add preserve-3d to self.element
|
|
elementCss["-webkit-transform-style"] = "preserve-3d";
|
|
}
|
|
|
|
|
|
faces.css(faceElementCss).find('*').css({
|
|
"backface-visibility": "hidden"
|
|
});
|
|
|
|
self.element.css(elementCss);
|
|
self.backElement.css(backElementCss);
|
|
|
|
// #39
|
|
// not forcing width/height may cause an initial flip to show up on
|
|
// page load when we apply the style to reverse the backface...
|
|
// To prevent self we first apply the basic styles and then give the
|
|
// browser a moment to apply them. Only afterwards do we add the transition.
|
|
setTimeout(function() {
|
|
// By now the browser should have applied the styles, so the transition
|
|
// will only affect subsequent flips.
|
|
var speedInSec = self.setting.speed / 1000 || 0.5;
|
|
faces.css({
|
|
"transition": "all " + speedInSec + "s ease-out"
|
|
});
|
|
|
|
// This allows flip to be called for setup with only a callback (default settings)
|
|
if (typeof callback === 'function') {
|
|
callback.call(self.element);
|
|
}
|
|
|
|
// While this used to work with a setTimeout of zero, at some point that became
|
|
// unstable and the initial flip returned. The reason for this is unknown but we
|
|
// will temporarily use a short delay of 20 to mitigate this issue.
|
|
}, 20);
|
|
|
|
self.attachEvents();
|
|
},
|
|
|
|
clickHandler: function(event) {
|
|
if (!event) { event = window.event; }
|
|
if (this.element.find($(event.target).closest('button, a, input[type="submit"]')).length) {
|
|
return;
|
|
}
|
|
|
|
if (this.isFlipped) {
|
|
this.unflip();
|
|
} else {
|
|
this.flip();
|
|
}
|
|
},
|
|
|
|
hoverHandler: function() {
|
|
var self = this;
|
|
self.element.off('mouseleave.flip');
|
|
|
|
self.flip();
|
|
|
|
setTimeout(function() {
|
|
self.element.on('mouseleave.flip', $.proxy(self.unflip, self));
|
|
if (!self.element.is(":hover")) {
|
|
self.unflip();
|
|
}
|
|
}, (self.setting.speed + 150));
|
|
},
|
|
|
|
attachEvents: function() {
|
|
var self = this;
|
|
if (self.setting.trigger === "click") {
|
|
self.element.on($.fn.tap ? "tap.flip" : "click.flip", $.proxy(self.clickHandler, self));
|
|
} else if (self.setting.trigger === "hover") {
|
|
self.element.on('mouseenter.flip', $.proxy(self.hoverHandler, self));
|
|
self.element.on('mouseleave.flip', $.proxy(self.unflip, self));
|
|
}
|
|
},
|
|
|
|
flipChanged: function(callback) {
|
|
this.element.trigger('flip:change');
|
|
if (typeof callback === 'function') {
|
|
callback.call(this.element);
|
|
}
|
|
},
|
|
|
|
changeSettings: function(options, callback) {
|
|
var self = this;
|
|
var changeNeeded = false;
|
|
|
|
if (options.axis !== undefined && self.setting.axis !== options.axis.toLowerCase()) {
|
|
self.setting.axis = options.axis.toLowerCase();
|
|
changeNeeded = true;
|
|
}
|
|
|
|
if (options.reverse !== undefined && self.setting.reverse !== options.reverse) {
|
|
self.setting.reverse = options.reverse;
|
|
changeNeeded = true;
|
|
}
|
|
|
|
if (changeNeeded) {
|
|
var faces = self.frontElement.add(self.backElement);
|
|
var savedTrans = faces.css(["transition-property", "transition-timing-function", "transition-duration", "transition-delay"]);
|
|
|
|
faces.css({
|
|
transition: "none"
|
|
});
|
|
|
|
// This sets up the first flip in the new direction automatically
|
|
var rotateAxis = "rotate" + self.setting.axis;
|
|
|
|
if (self.isFlipped) {
|
|
self.frontElement.css({
|
|
transform: rotateAxis + (self.setting.reverse ? "(-180deg)" : "(180deg)"),
|
|
"z-index": "0"
|
|
});
|
|
} else {
|
|
self.backElement.css({
|
|
transform: rotateAxis + (self.setting.reverse ? "(180deg)" : "(-180deg)"),
|
|
"z-index": "0"
|
|
});
|
|
}
|
|
// Providing a nicely wrapped up callback because transform is essentially async
|
|
setTimeout(function() {
|
|
faces.css(savedTrans);
|
|
self.flipChanged(callback);
|
|
}, 0);
|
|
} else {
|
|
// If we didnt have to set the axis we can just call back.
|
|
self.flipChanged(callback);
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
/*
|
|
* jQuery collection methods
|
|
*/
|
|
$.fn.flip = function (options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
}
|
|
|
|
if (typeof options === "string" || typeof options === "boolean") {
|
|
this.each(function() {
|
|
var flip = $(this).data('flip-model');
|
|
|
|
if (options === "toggle") {
|
|
options = !flip.isFlipped;
|
|
}
|
|
|
|
if (options) {
|
|
flip.flip(callback);
|
|
} else {
|
|
flip.unflip(callback);
|
|
}
|
|
});
|
|
} else {
|
|
this.each(function() {
|
|
if ($(this).data('flip-model')) { // The element has been initiated, all we have to do is change applicable settings
|
|
var flip = $(this).data('flip-model');
|
|
|
|
if (options && (options.axis !== undefined || options.reverse !== undefined)) {
|
|
flip.changeSettings(options, callback);
|
|
}
|
|
} else { // Init
|
|
$(this).data('flip-model', new Flip($(this), (options || {}), callback));
|
|
}
|
|
});
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
}( jQuery ));
|