463 lines
15 KiB
JavaScript
463 lines
15 KiB
JavaScript
|
/*! UIkit 2.27.4 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */
|
||
|
(function(addon) {
|
||
|
|
||
|
var component;
|
||
|
|
||
|
if (window.UIkit2) {
|
||
|
component = addon(UIkit2);
|
||
|
}
|
||
|
|
||
|
if (typeof define == 'function' && define.amd) {
|
||
|
define('uikit-parallax', ['uikit'], function(){
|
||
|
return component || addon(UIkit2);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
})(function(UI){
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
var parallaxes = [],
|
||
|
supports3d = false,
|
||
|
scrolltop = 0,
|
||
|
wh = window.innerHeight,
|
||
|
checkParallaxes = function() {
|
||
|
|
||
|
scrolltop = UI.$win.scrollTop();
|
||
|
|
||
|
window.requestAnimationFrame(function(){
|
||
|
for (var i=0; i < parallaxes.length; i++) {
|
||
|
parallaxes[i].process();
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
|
||
|
UI.component('parallax', {
|
||
|
|
||
|
defaults: {
|
||
|
velocity : 0.5,
|
||
|
target : false,
|
||
|
viewport : false,
|
||
|
media : false
|
||
|
},
|
||
|
|
||
|
boot: function() {
|
||
|
|
||
|
supports3d = (function(){
|
||
|
|
||
|
var el = document.createElement('div'),
|
||
|
has3d,
|
||
|
transforms = {
|
||
|
'WebkitTransform':'-webkit-transform',
|
||
|
'MSTransform':'-ms-transform',
|
||
|
'MozTransform':'-moz-transform',
|
||
|
'Transform':'transform'
|
||
|
};
|
||
|
|
||
|
// Add it to the body to get the computed style.
|
||
|
document.body.insertBefore(el, null);
|
||
|
|
||
|
for (var t in transforms) {
|
||
|
if (el.style[t] !== undefined) {
|
||
|
el.style[t] = 'translate3d(1px,1px,1px)';
|
||
|
has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
document.body.removeChild(el);
|
||
|
|
||
|
return (has3d !== undefined && has3d.length > 0 && has3d !== "none");
|
||
|
})();
|
||
|
|
||
|
// listen to scroll and resize
|
||
|
UI.$doc.on('scrolling.uk.document', checkParallaxes);
|
||
|
UI.$win.on('load resize orientationchange', UI.Utils.debounce(function(){
|
||
|
wh = window.innerHeight;
|
||
|
checkParallaxes();
|
||
|
}, 50));
|
||
|
|
||
|
// init code
|
||
|
UI.ready(function(context) {
|
||
|
|
||
|
UI.$('[data-uk-parallax]', context).each(function() {
|
||
|
|
||
|
var parallax = UI.$(this);
|
||
|
|
||
|
if (!parallax.data('parallax')) {
|
||
|
UI.parallax(parallax, UI.Utils.options(parallax.attr('data-uk-parallax')));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
|
||
|
init: function() {
|
||
|
|
||
|
this.base = this.options.target ? UI.$(this.options.target) : this.element;
|
||
|
this.props = {};
|
||
|
this.velocity = (this.options.velocity || 1);
|
||
|
|
||
|
var reserved = ['target','velocity','viewport','plugins','media'];
|
||
|
|
||
|
Object.keys(this.options).forEach(function(prop){
|
||
|
|
||
|
if (reserved.indexOf(prop) !== -1) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var start, end, dir, diff, startend = String(this.options[prop]).split(',');
|
||
|
|
||
|
if (prop.match(/color/i)) {
|
||
|
start = startend[1] ? startend[0] : this._getStartValue(prop),
|
||
|
end = startend[1] ? startend[1] : startend[0];
|
||
|
|
||
|
if (!start) {
|
||
|
start = 'rgba(255,255,255,0)';
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
start = parseFloat(startend[1] ? startend[0] : this._getStartValue(prop)),
|
||
|
end = parseFloat(startend[1] ? startend[1] : startend[0]);
|
||
|
diff = (start < end ? (end-start):(start-end));
|
||
|
dir = (start < end ? 1:-1);
|
||
|
}
|
||
|
|
||
|
this.props[prop] = { start: start, end: end, dir: dir, diff: diff };
|
||
|
|
||
|
}.bind(this));
|
||
|
|
||
|
parallaxes.push(this);
|
||
|
},
|
||
|
|
||
|
process: function() {
|
||
|
|
||
|
if (this.options.media) {
|
||
|
|
||
|
switch(typeof(this.options.media)) {
|
||
|
case 'number':
|
||
|
if (window.innerWidth < this.options.media) {
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
case 'string':
|
||
|
if (window.matchMedia && !window.matchMedia(this.options.media).matches) {
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var percent = this.percentageInViewport();
|
||
|
|
||
|
if (this.options.viewport !== false) {
|
||
|
percent = (this.options.viewport === 0) ? 1 : percent / this.options.viewport;
|
||
|
}
|
||
|
|
||
|
this.update(percent);
|
||
|
},
|
||
|
|
||
|
percentageInViewport: function() {
|
||
|
|
||
|
var top = this.base.offset().top,
|
||
|
height = this.base.outerHeight(),
|
||
|
distance, percentage, percent;
|
||
|
|
||
|
if (top > (scrolltop + wh)) {
|
||
|
percent = 0;
|
||
|
} else if ((top + height) < scrolltop) {
|
||
|
percent = 1;
|
||
|
} else {
|
||
|
|
||
|
if ((top + height) < wh) {
|
||
|
|
||
|
percent = (scrolltop < wh ? scrolltop : scrolltop - wh) / (top+height);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
distance = (scrolltop + wh) - top;
|
||
|
percentage = Math.round(distance / ((wh + height) / 100));
|
||
|
percent = percentage/100;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return percent;
|
||
|
},
|
||
|
|
||
|
update: function(percent) {
|
||
|
|
||
|
var $this = this,
|
||
|
css = {transform:'', filter:''},
|
||
|
compercent = percent * (1 - (this.velocity - (this.velocity * percent))),
|
||
|
opts, val;
|
||
|
|
||
|
if (compercent < 0) compercent = 0;
|
||
|
if (compercent > 1) compercent = 1;
|
||
|
|
||
|
if (this._percent !== undefined && this._percent == compercent) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Object.keys(this.props).forEach(function(prop) {
|
||
|
|
||
|
opts = this.props[prop];
|
||
|
|
||
|
if (percent === 0) {
|
||
|
val = opts.start;
|
||
|
} else if(percent === 1) {
|
||
|
val = opts.end;
|
||
|
} else if(opts.diff !== undefined) {
|
||
|
val = opts.start + (opts.diff * compercent * opts.dir);
|
||
|
}
|
||
|
|
||
|
if ((prop == 'bg' || prop == 'bgp') && !this._bgcover) {
|
||
|
this._bgcover = initBgImageParallax(this, prop, opts);
|
||
|
}
|
||
|
|
||
|
switch(prop) {
|
||
|
|
||
|
// transforms
|
||
|
case 'x':
|
||
|
css.transform += supports3d ? ' translate3d('+val+'px, 0, 0)':' translateX('+val+'px)';
|
||
|
break;
|
||
|
case 'xp':
|
||
|
css.transform += supports3d ? ' translate3d('+val+'%, 0, 0)':' translateX('+val+'%)';
|
||
|
break;
|
||
|
case 'y':
|
||
|
css.transform += supports3d ? ' translate3d(0, '+val+'px, 0)':' translateY('+val+'px)';
|
||
|
break;
|
||
|
case 'yp':
|
||
|
css.transform += supports3d ? ' translate3d(0, '+val+'%, 0)':' translateY('+val+'%)';
|
||
|
break;
|
||
|
case 'rotate':
|
||
|
css.transform += ' rotate('+val+'deg)';
|
||
|
break;
|
||
|
case 'scale':
|
||
|
css.transform += ' scale('+val+')';
|
||
|
break;
|
||
|
|
||
|
// bg image
|
||
|
case 'bg':
|
||
|
|
||
|
// don't move if image height is too small
|
||
|
// if ($this.element.data('bgsize') && ($this.element.data('bgsize').h + val - window.innerHeight) < 0) {
|
||
|
// break;
|
||
|
// }
|
||
|
|
||
|
css['background-position'] = '50% '+val+'px';
|
||
|
break;
|
||
|
case 'bgp':
|
||
|
css['background-position'] = '50% '+val+'%';
|
||
|
break;
|
||
|
|
||
|
// color
|
||
|
case 'color':
|
||
|
case 'background-color':
|
||
|
case 'border-color':
|
||
|
css[prop] = calcColor(opts.start, opts.end, compercent);
|
||
|
break;
|
||
|
|
||
|
// CSS Filter
|
||
|
case 'blur':
|
||
|
css.filter += ' blur('+val+'px)';
|
||
|
break;
|
||
|
case 'hue':
|
||
|
css.filter += ' hue-rotate('+val+'deg)';
|
||
|
break;
|
||
|
case 'grayscale':
|
||
|
css.filter += ' grayscale('+val+'%)';
|
||
|
break;
|
||
|
case 'invert':
|
||
|
css.filter += ' invert('+val+'%)';
|
||
|
break;
|
||
|
case 'fopacity':
|
||
|
css.filter += ' opacity('+val+'%)';
|
||
|
break;
|
||
|
case 'saturate':
|
||
|
css.filter += ' saturate('+val+'%)';
|
||
|
break;
|
||
|
case 'sepia':
|
||
|
css.filter += ' sepia('+val+'%)';
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
css[prop] = val;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}.bind(this));
|
||
|
|
||
|
if (css.filter) {
|
||
|
css['-webkit-filter'] = css.filter;
|
||
|
}
|
||
|
|
||
|
this.element.css(css);
|
||
|
|
||
|
this._percent = compercent;
|
||
|
},
|
||
|
|
||
|
_getStartValue: function(prop) {
|
||
|
|
||
|
var value = 0;
|
||
|
|
||
|
switch(prop) {
|
||
|
case 'scale':
|
||
|
value = 1;
|
||
|
break;
|
||
|
default:
|
||
|
value = this.element.css(prop);
|
||
|
}
|
||
|
|
||
|
return (value || 0);
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
|
||
|
// helper
|
||
|
|
||
|
function initBgImageParallax(obj, prop, opts) {
|
||
|
|
||
|
var img = new Image(), url, element, size, check, ratio, width, height;
|
||
|
|
||
|
element = obj.element.css({backgroundSize: 'cover', backgroundRepeat: 'no-repeat'});
|
||
|
url = element.css('background-image').replace(/^url\(/g, '').replace(/\)$/g, '').replace(/("|')/g, '');
|
||
|
check = function() {
|
||
|
|
||
|
var w = element.innerWidth(), h = element.innerHeight(), extra = (prop=='bg') ? opts.diff : (opts.diff/100) * h;
|
||
|
|
||
|
h += extra;
|
||
|
w += Math.ceil(extra * ratio);
|
||
|
|
||
|
if (w-extra < size.w && h < size.h) {
|
||
|
return obj.element.css({backgroundSize: 'auto'});
|
||
|
}
|
||
|
|
||
|
// if element height < parent height (gap underneath)
|
||
|
if ((w / ratio) < h) {
|
||
|
|
||
|
width = Math.ceil(h * ratio);
|
||
|
height = h;
|
||
|
|
||
|
if (h > window.innerHeight) {
|
||
|
width = width * 1.2;
|
||
|
height = height * 1.2;
|
||
|
}
|
||
|
|
||
|
// element width < parent width (gap to right)
|
||
|
} else {
|
||
|
|
||
|
width = w;
|
||
|
height = Math.ceil(w / ratio);
|
||
|
}
|
||
|
|
||
|
element.css({backgroundSize: (width+'px '+height+'px')}).data('bgsize', {w:width,h:height});
|
||
|
};
|
||
|
|
||
|
img.onerror = function(){
|
||
|
// image url doesn't exist
|
||
|
};
|
||
|
|
||
|
img.onload = function(){
|
||
|
size = {w:img.width, h:img.height};
|
||
|
ratio = img.width / img.height;
|
||
|
|
||
|
UI.$win.on('load resize orientationchange', UI.Utils.debounce(function(){
|
||
|
check();
|
||
|
}, 50));
|
||
|
|
||
|
check();
|
||
|
};
|
||
|
|
||
|
img.src = url;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Some named colors to work with, added by Bradley Ayers
|
||
|
// From Interface by Stefan Petre
|
||
|
// http://interface.eyecon.ro/
|
||
|
var colors = {
|
||
|
'black': [0,0,0,1],
|
||
|
'blue': [0,0,255,1],
|
||
|
'brown': [165,42,42,1],
|
||
|
'cyan': [0,255,255,1],
|
||
|
'fuchsia': [255,0,255,1],
|
||
|
'gold': [255,215,0,1],
|
||
|
'green': [0,128,0,1],
|
||
|
'indigo': [75,0,130,1],
|
||
|
'khaki': [240,230,140,1],
|
||
|
'lime': [0,255,0,1],
|
||
|
'magenta': [255,0,255,1],
|
||
|
'maroon': [128,0,0,1],
|
||
|
'navy': [0,0,128,1],
|
||
|
'olive': [128,128,0,1],
|
||
|
'orange': [255,165,0,1],
|
||
|
'pink': [255,192,203,1],
|
||
|
'purple': [128,0,128,1],
|
||
|
'violet': [128,0,128,1],
|
||
|
'red': [255,0,0,1],
|
||
|
'silver': [192,192,192,1],
|
||
|
'white': [255,255,255,1],
|
||
|
'yellow': [255,255,0,1],
|
||
|
'transparent': [255,255,255,0]
|
||
|
};
|
||
|
|
||
|
function calcColor(start, end, pos) {
|
||
|
|
||
|
start = parseColor(start);
|
||
|
end = parseColor(end);
|
||
|
pos = pos || 0;
|
||
|
|
||
|
return calculateColor(start, end, pos);
|
||
|
}
|
||
|
|
||
|
/**!
|
||
|
* @preserve Color animation 1.6.0
|
||
|
* http://www.bitstorm.org/jquery/color-animation/
|
||
|
* Copyright 2011, 2013 Edwin Martin <edwin@bitstorm.org>
|
||
|
* Released under the MIT and GPL licenses.
|
||
|
*/
|
||
|
|
||
|
// Calculate an in-between color. Returns "#aabbcc"-like string.
|
||
|
function calculateColor(begin, end, pos) {
|
||
|
var color = 'rgba('
|
||
|
+ parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ','
|
||
|
+ parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ','
|
||
|
+ parseInt((begin[2] + pos * (end[2] - begin[2])), 10) + ','
|
||
|
+ (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1);
|
||
|
|
||
|
color += ')';
|
||
|
return color;
|
||
|
}
|
||
|
|
||
|
// Parse an CSS-syntax color. Outputs an array [r, g, b]
|
||
|
function parseColor(color) {
|
||
|
|
||
|
var match, quadruplet;
|
||
|
|
||
|
// Match #aabbcc
|
||
|
if (match = /#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(color)) {
|
||
|
quadruplet = [parseInt(match[1], 16), parseInt(match[2], 16), parseInt(match[3], 16), 1];
|
||
|
|
||
|
// Match #abc
|
||
|
} else if (match = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/.exec(color)) {
|
||
|
quadruplet = [parseInt(match[1], 16) * 17, parseInt(match[2], 16) * 17, parseInt(match[3], 16) * 17, 1];
|
||
|
|
||
|
// Match rgb(n, n, n)
|
||
|
} else if (match = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) {
|
||
|
quadruplet = [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), 1];
|
||
|
|
||
|
} else if (match = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9\.]*)\s*\)/.exec(color)) {
|
||
|
quadruplet = [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10),parseFloat(match[4])];
|
||
|
|
||
|
// No browser returns rgb(n%, n%, n%), so little reason to support this format.
|
||
|
} else {
|
||
|
quadruplet = colors[color] || [255,255,255,0];
|
||
|
}
|
||
|
return quadruplet;
|
||
|
}
|
||
|
|
||
|
return UI.parallax;
|
||
|
});
|