From 01b6a365937be40123848e370e0641c3ef44005f Mon Sep 17 00:00:00 2001 From: Brendan Mullins Date: Sun, 16 Apr 2017 22:59:19 +0200 Subject: [PATCH 1/2] beta release --- README.md | 8 +- src/vue-parallax-js.js | 327 ++++++++++++++++++++++++++++------------- vue-parallax-js.js | 314 +++++++++++++++++++++++++++------------ 3 files changed, 448 insertions(+), 201 deletions(-) diff --git a/README.md b/README.md index 95c0082..a61d53f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ +#BETA +- values range changed from 0-1 to 0-10 +- you can define "percentage" like this: v-parallax="{speed: 3, percentage: 1}" more more fine control + #vue-parallax-js vue component for parallax effect on elements. - no dependencies. -- for Vue.js 2 - lightweight -- 1.6 kb minified ##Setup ```bash @@ -25,7 +27,7 @@ Vue.use(VueParallaxJs) ##Usage when everything is setup you can use the directive like this: ```html -

vue-parallax-js

+

vue-parallax-js

``` ##Options and Modifiers diff --git a/src/vue-parallax-js.js b/src/vue-parallax-js.js index 2425891..491810c 100644 --- a/src/vue-parallax-js.js +++ b/src/vue-parallax-js.js @@ -1,114 +1,239 @@ -let parallaxjs = function (options) { - this.options = options -} +let gop = {}; + +const vueParallaxJS = function(el, options) { + var self = Object.create(vueParallaxJS.prototype); + + var posY = 0; + var screenY = 0; + var blocks = []; + var pause = false; + + let active = true; + let minWidth = gop.minWidth || 0; + + var loop = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.msRequestAnimationFrame || + window.oRequestAnimationFrame || + function(callback) { + setTimeout(callback, 1000 / 60); + }; + + // check which transform property to use + var transformProp = window.transformProp || (function() { + var testEl = document.createElement('div'); + if (testEl.style.transform == null) { + var vendors = ['Webkit', 'Moz', 'ms']; + for (var vendor in vendors) { + if (testEl.style[vendors[vendor] + 'Transform'] !== undefined) { + return vendors[vendor] + 'Transform'; + } + } + } + return 'transform'; + })(); + + // limit the given number in the range [min, max] + var clamp = function(num, min, max) { + return (num <= min) ? min : ((num >= max) ? max : num); + }; + + self.options = { + speed: -2, + center: false, + round: true, + }; + + + // self.options.speed = clamp(self.options.speed, -10, 10); + + + let elements = [el] + + // Now query selector + if (elements.length > 0) { + self.elems = elements; + } -parallaxjs.prototype = { - items: [], - active: true, - - setStyle (item, value) { - if (item.modifiers.centerX) - value += ' translateX(-50%)' - - let el = item.el; - let prop = 'Transform'; - el.style["webkit" + prop] = value; - el.style["moz" + prop] = value; - el.style["ms" + prop] = value; - }, - - add (el, binding) { - let value = binding.value - let arg = binding.arg - let style = el.currentStyle || window.getComputedStyle(el); - - let height = binding.modifiers.absY ? window.innerHeight : el.clientHeight || el.offsetHeight || el.scrollHeight; - this.items.push({ - el: el, - initialOffsetTop: el.offsetTop + el.offsetParent.offsetTop - parseInt(style.marginTop), - style, - value, - arg, - modifiers: binding.modifiers, - clientHeight: height, - count: 0 - }) - }, - - move () { - if (!this.active) return - if (window.innerWidth < this.options.minWidth || 0) { - this.items.map((item) => { - this.setStyle(item, 'translateY(' + 0 + 'px) translateZ(0px)') - }) - - return + // The elements don't exist + else { + throw new Error("The elements you're trying to select don't exist."); } - let scrollTop = window.scrollY || window.pageYOffset - let windowHeight = window.innerHeight - let windowWidth = window.innerWidth - this.items.map((item) => { - let pos = (scrollTop + windowHeight) - let elH = item.clientHeight - // if (item.count > 50) { - // item.count = 0; - // elH = item.el.clientHeight || item.el.offsetHeight || item.el.scrollHeight - // } + // Let's kick this script off + // Build array for cached element values + // Bind scroll and resize to animate method + var init = function() { + screenY = window.innerHeight; + setPosition(); + + // Get and cache initial position of all elements + for (var i = 0; i < self.elems.length; i++) { + var block = createBlock(self.elems[i], options); + blocks.push(block); + } + + window.addEventListener('resize', function() { + animate(); + }); + + // Start the loop + update(); + animate(); + }; + + + var createBlock = function(el, options) { + var dataPercentage = options.percentage; + var dataSpeed = options.speed; + + + var posY = dataPercentage || self.options.center ? (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop) : 0; + + var blockTop = posY + el.getBoundingClientRect().top; + var blockHeight = el.clientHeight || el.offsetHeight || el.scrollHeight; + + var percentage = dataPercentage ? dataPercentage : (posY - blockTop + screenY) / (blockHeight + screenY); + if (self.options.center) { + percentage = 0.5; + } + + var speed = dataSpeed ? clamp(dataSpeed, -10, 10) : self.options.speed; + if (dataPercentage || self.options.center) { + speed = clamp(dataSpeed || self.options.speed, -5, 5); + } + + var base = updatePosition(percentage, speed); + + var style = el.style.cssText; + var transform = ''; + + if (style.indexOf('transform') >= 0) { + // Get the index of the transform + var index = style.indexOf('transform'); + var trimmedStyle = style.slice(index); + var delimiter = trimmedStyle.indexOf(';'); - pos = pos - (elH / 2) - pos = pos - (windowHeight / 2) - pos = pos * item.value + if (delimiter) { + transform = " " + trimmedStyle.slice(11, delimiter).replace(/\s/g, ''); + } else { + transform = " " + trimmedStyle.slice(11).replace(/\s/g, ''); + } + } - let offset = item.initialOffsetTop - offset = offset * -1 - offset = offset * item.value - pos = pos + offset + return { + base: base, + top: blockTop, + height: blockHeight, + speed: speed, + style: style, + transform: transform + }; + }; - pos = pos.toFixed(2) + var setPosition = function() { + var oldY = posY; - // item.count++ - this.setStyle(item, 'translateY(' + pos + 'px)') - }) - } + if (window.pageYOffset !== undefined) { + posY = window.pageYOffset; + } else { + posY = (document.documentElement || document.body.parentNode || document.body).scrollTop; + } + + if (oldY != posY) { + // scroll changed, return true + return true; + } + + // scroll did not change + return false; + }; + + + var updatePosition = function(percentage, speed) { + + var value = (speed * (100 * (1 - percentage))); + return self.options.round ? Math.round(value) : value; + + }; + + + // + var update = function() { + if (setPosition() && pause === false) { + animate(); + } + + // loop again + loop(update); + }; + + // Transform3d on parallax element + var animate = function() { + for (var i = 0; i < self.elems.length; i++) { + var percentage = ((posY - blocks[i].top + screenY) / (blocks[i].height + screenY)); + + // Subtracting initialize value, so element stays in same spot as HTML + var position; + if (window.innerWidth >= minWidth && parallaxjs.api.active === true) { + position = updatePosition(percentage, blocks[i].speed) - blocks[i].base; + } else { + position = 0 + } + + // Move that element + // (Set the new translation and append initial inline transforms.) + var translate = 'translate3d(0,' + position + 'px,0) ' + blocks[i].transform; + self.elems[i].style[transformProp] = translate; + } + }; + + + self.destroy = function() { + for (var i = 0; i < self.elems.length; i++) { + self.elems[i].style.cssText = blocks[i].style; + } + pause = true; + }; + + + init(); + return self; +}; + + +const parallaxjs = { + api: { + active: true + }, + add(el, binding) { + if (el.parallax) + return + + let options = {}; + if (typeof val === 'number') { + options.speed = binding.value + } else { + options = binding.value + } + + el.parallax = new vueParallaxJS(el, options); + } } export default { - install (Vue, options = {}) { - var p = new parallaxjs(options) - - window.addEventListener('scroll', () => { - requestAnimationFrame(() => { - p.move(p) - }) - }, {passive: true}) - window.addEventListener('resize', () => { - requestAnimationFrame(() => { - p.move(p) - }) - }, {passive: true}) - - Vue.prototype.$parallaxjs = p - window.$parallaxjs = p - Vue.directive('parallax', { - bind (el, binding) { - }, - inserted (el, binding) { - p.add(el, binding) - p.move(p) - }, - // unbind(el, binding) { - // p.remove(el) - // } - // bind: parallaxjs.add(parallaxjs), - // update(value) { - // parallaxjs.update(value) - // }, - // update(el, binding) { - // console.log("cup"); - // }, - }) - } + install(Vue, options = {}) { + gop = options; + window.parallaxjs = parallaxjs.api; + Vue.directive('parallax', { + bind(el, binding) { + }, + inserted(el, binding) { + parallaxjs.add(el, binding) + }, + }) + } } diff --git a/vue-parallax-js.js b/vue-parallax-js.js index ec2430d..180ada2 100644 --- a/vue-parallax-js.js +++ b/vue-parallax-js.js @@ -1,104 +1,224 @@ -var parallaxjs = function parallaxjs(options) { - this.options = options; -}; - -parallaxjs.prototype = { - items: [], - active: true, - - setStyle: function setStyle(item, value) { - if (item.modifiers.centerX) value += ' translateX(-50%)'; - - var el = item.el; - var prop = 'Transform'; - el.style["webkit" + prop] = value; - el.style["moz" + prop] = value; - el.style["ms" + prop] = value; - }, - add: function add(el, binding) { - var value = binding.value; - var arg = binding.arg; - var style = el.currentStyle || window.getComputedStyle(el); - - var height = binding.modifiers.absY ? window.innerHeight : el.clientHeight || el.offsetHeight || el.scrollHeight; - this.items.push({ - el: el, - initialOffsetTop: el.offsetTop + el.offsetParent.offsetTop - parseInt(style.marginTop), - style: style, - value: value, - arg: arg, - modifiers: binding.modifiers, - clientHeight: height, - count: 0 - }); - }, - move: function move() { - var _this = this; - - if (!this.active) return; - if (window.innerWidth < this.options.minWidth || 0) { - this.items.map(function (item) { - _this.setStyle(item, 'translateY(' + 0 + 'px) translateZ(0px)'); - }); - - return; +var gop = {}; + +var vueParallaxJS = function vueParallaxJS(el, options) { + var self = Object.create(vueParallaxJS.prototype); + + var posY = 0; + var screenY = 0; + var blocks = []; + var pause = false; + + var active = true; + var minWidth = gop.minWidth || 0; + + var loop = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function (callback) { + setTimeout(callback, 1000 / 60); + }; + + // check which transform property to use + var transformProp = window.transformProp || function () { + var testEl = document.createElement('div'); + if (testEl.style.transform == null) { + var vendors = ['Webkit', 'Moz', 'ms']; + for (var vendor in vendors) { + if (testEl.style[vendors[vendor] + 'Transform'] !== undefined) { + return vendors[vendor] + 'Transform'; + } + } + } + return 'transform'; + }(); + + // limit the given number in the range [min, max] + var clamp = function clamp(num, min, max) { + return num <= min ? min : num >= max ? max : num; + }; + + self.options = { + speed: -2, + center: false, + round: true + }; + + // self.options.speed = clamp(self.options.speed, -10, 10); + + + var elements = [el]; + + // Now query selector + if (elements.length > 0) { + self.elems = elements; } - var scrollTop = window.scrollY || window.pageYOffset; - var windowHeight = window.innerHeight; - var windowWidth = window.innerWidth; - - this.items.map(function (item) { - var pos = scrollTop + windowHeight; - var elH = item.clientHeight; - // if (item.count > 50) { - // item.count = 0; - // elH = item.el.clientHeight || item.el.offsetHeight || item.el.scrollHeight - // } - - - pos = pos - elH / 2; - pos = pos - windowHeight / 2; - pos = pos * item.value; - - var offset = item.initialOffsetTop; - offset = offset * -1; - offset = offset * item.value; - pos = pos + offset; - - pos = pos.toFixed(2); + // The elements don't exist + else { + throw new Error("The elements you're trying to select don't exist."); + } + + // Let's kick this script off + // Build array for cached element values + // Bind scroll and resize to animate method + var init = function init() { + screenY = window.innerHeight; + setPosition(); + + // Get and cache initial position of all elements + for (var i = 0; i < self.elems.length; i++) { + var block = createBlock(self.elems[i], options); + blocks.push(block); + } + + window.addEventListener('resize', function () { + animate(); + }); + + // Start the loop + update(); + animate(); + }; + + var createBlock = function createBlock(el, options) { + var dataPercentage = options.percentage; + var dataSpeed = options.speed; + + var posY = dataPercentage || self.options.center ? window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop : 0; + + var blockTop = posY + el.getBoundingClientRect().top; + var blockHeight = el.clientHeight || el.offsetHeight || el.scrollHeight; + + var percentage = dataPercentage ? dataPercentage : (posY - blockTop + screenY) / (blockHeight + screenY); + if (self.options.center) { + percentage = 0.5; + } + + var speed = dataSpeed ? clamp(dataSpeed, -10, 10) : self.options.speed; + if (dataPercentage || self.options.center) { + speed = clamp(dataSpeed || self.options.speed, -5, 5); + } + + var base = updatePosition(percentage, speed); + + var style = el.style.cssText; + var transform = ''; + + if (style.indexOf('transform') >= 0) { + // Get the index of the transform + var index = style.indexOf('transform'); + + var trimmedStyle = style.slice(index); + var delimiter = trimmedStyle.indexOf(';'); + + if (delimiter) { + transform = " " + trimmedStyle.slice(11, delimiter).replace(/\s/g, ''); + } else { + transform = " " + trimmedStyle.slice(11).replace(/\s/g, ''); + } + } + + return { + base: base, + top: blockTop, + height: blockHeight, + speed: speed, + style: style, + transform: transform + }; + }; + + var setPosition = function setPosition() { + var oldY = posY; + + if (window.pageYOffset !== undefined) { + posY = window.pageYOffset; + } else { + posY = (document.documentElement || document.body.parentNode || document.body).scrollTop; + } + + if (oldY != posY) { + // scroll changed, return true + return true; + } + + // scroll did not change + return false; + }; + + var updatePosition = function updatePosition(percentage, speed) { + + var value = speed * (100 * (1 - percentage)); + return self.options.round ? Math.round(value) : value; + }; + + // + var update = function update() { + if (setPosition() && pause === false) { + animate(); + } + + // loop again + loop(update); + }; + + // Transform3d on parallax element + var animate = function animate() { + for (var i = 0; i < self.elems.length; i++) { + var percentage = (posY - blocks[i].top + screenY) / (blocks[i].height + screenY); + + // Subtracting initialize value, so element stays in same spot as HTML + var position; + if (window.innerWidth >= minWidth && parallaxjs.api.active === true) { + position = updatePosition(percentage, blocks[i].speed) - blocks[i].base; + } else { + position = 0; + } + + // Move that element + // (Set the new translation and append initial inline transforms.) + var translate = 'translate3d(0,' + position + 'px,0) ' + blocks[i].transform; + self.elems[i].style[transformProp] = translate; + } + }; + + self.destroy = function () { + for (var i = 0; i < self.elems.length; i++) { + self.elems[i].style.cssText = blocks[i].style; + } + pause = true; + }; + + init(); + return self; +}; - // item.count++ - _this.setStyle(item, 'translateY(' + pos + 'px)'); - }); - } +var parallaxjs = { + api: { + active: true + }, + add: function add(el, binding) { + if (el.parallax) return; + + var options = {}; + if (typeof val === 'number') { + options.speed = binding.value; + } else { + options = binding.value; + } + + el.parallax = new vueParallaxJS(el, options); + } }; export default { - install: function install(Vue) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - var p = new parallaxjs(options); - - window.addEventListener('scroll', function () { - requestAnimationFrame(function () { - p.move(p); - }); - }, { passive: true }); - window.addEventListener('resize', function () { - requestAnimationFrame(function () { - p.move(p); - }); - }, { passive: true }); - - Vue.prototype.$parallaxjs = p; - window.$parallaxjs = p; - Vue.directive('parallax', { - bind: function bind(el, binding) {}, - inserted: function inserted(el, binding) { - p.add(el, binding); - p.move(p); - } - }); - } + install: function install(Vue) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + gop = options; + window.parallaxjs = parallaxjs.api; + Vue.directive('parallax', { + bind: function bind(el, binding) {}, + inserted: function inserted(el, binding) { + parallaxjs.add(el, binding); + } + }); + } }; From 50540f884ccdbe779130d2712f6af1123e77bbc4 Mon Sep 17 00:00:00 2001 From: Brendan Mullins Date: Sun, 16 Apr 2017 23:00:25 +0200 Subject: [PATCH 2/2] up version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 994f9d8..9d37a8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-parallax-js", - "version": "0.0.4", + "version": "0.1.0", "description": "Vue component for easy parallax with directive", "main": "vue-parallax-js.js", "directories": {