From d6cf236b4bb84d4e7e86dd412d71efcdfcb35263 Mon Sep 17 00:00:00 2001 From: stefanorigano Date: Thu, 3 Nov 2016 13:21:44 +0100 Subject: [PATCH] [REF] website: refactor parallax snippet This commit is a refactoring of the 's_parallax' snippet. Note that it is compatible with previous parallax snippet (old snippets are just transformed into the brand new one on page load). Note that scroll speed are changed by this implementation though. This solves several issues: - Bg image went easily off of the frame if scroll speed was too fast - The 'scroll speed' effect was visually inconsistent across different devices. - If the Bg image was smaller than the frame (condition which occurs often in a responsive environment), then the image was totally offset. - Since the previous implementation use background-position property to perform the effect, It was not possible to define an image focus point. This implementation solves the previous issues introducing a safe-area that limit the effect range. It's calculated dynamically, according to the current screen size and the 'speed' option. The effect if now achieved thanks to the translation of a DOM element instead of background-position changes. This allows to solve the last mentionned problem and improve performance. The implementation rely on the new animation methods of the snippets Animation class. As this use the "requestAnimationFrame" function, the snippet knows the right time to repaint itself, instead of using the "scroll" event which is triggered too many times and thus occurs between painting frames. This change greatly improved performances (CPU/painting usage many times smaller). --- .../src/js/website.snippets.animation.js | 85 ++++++++++++------- .../static/src/js/website.snippets.editor.js | 41 ++++++--- .../static/src/less/website.snippets.less | 11 +++ addons/website/views/snippets.xml | 18 ++-- 4 files changed, 101 insertions(+), 54 deletions(-) diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js index 6a8a0257627..e4dd19e03db 100644 --- a/addons/website/static/src/js/website.snippets.animation.js +++ b/addons/website/static/src/js/website.snippets.animation.js @@ -314,52 +314,71 @@ animation.registry.slider = animation.Class.extend({ animation.registry.parallax = animation.Class.extend({ selector: ".parallax", - start: function () { - _.defer((function () { this.set_values(); }).bind(this)); - $(window).on("scroll.animation_parallax", _.throttle(this.on_scroll.bind(this), 10)) - .on("resize.animation_parallax", _.debounce(this.set_values.bind(this), 500)); - - return this._super.apply(this, arguments); + this._super.apply(this, arguments); + this._rebuild(); + $(window).on("resize.animation_parallax", _.debounce(this._rebuild.bind(this), 500)); }, stop: function () { + this._super.apply(this, arguments); $(window).off(".animation_parallax"); }, + _rebuild: function () { + // Add/find bg DOM element to hold the parallax bg (support old v10.0 parallax) + if (!this.$bg || !this.$bg.length) { + this.$bg = this.$("> .s_parallax_bg"); + if (!this.$bg.length) { + this.$bg = $("", {"class": "s_parallax_bg"}).prependTo(this.$target); + } + } + var urlTarget = this.$target.css("background-image"); + if (urlTarget !== "none") { + this.$bg.css("background-image", urlTarget); + } + this.$target.css("background-image", "none"); - set_values: function () { - var self = this; - this.speed = parseFloat(self.$target.attr("data-scroll-background-ratio") || 0); - this.offset = 0; + // Get parallax speed + this.speed = parseFloat(this.$target.attr("data-scroll-background-ratio") || 0); - if (this.speed === 1 || this.$target.css("background-image") === "none") { - this.$target.css("background-attachment", "fixed").css("background-position", "0px 0px"); + // Reset offset if parallax effect will not be performed and leave + this.$target.toggleClass("s_parallax_is_fixed", this.speed === 1); + if (this.speed === 0 || this.speed === 1) { + this.$bg.css({ + transform: "", + top: "", + bottom: "" + }); return; } - this.$target.css("background-attachment", "scroll"); + // Initialize parallax data according to snippet and viewport dimensions + this.viewport = document.body.clientHeight - $('#wrapwrap').position().top; + this.visible_area = [this.$target.offset().top]; + this.visible_area.push(this.visible_area[0] + this.$target.innerHeight() + this.viewport); + this.ratio = this.speed * (this.viewport / 10); - var img = new Image(); - img.onload = function () { - var offset = 0; - var padding = parseInt($(document.body).css("padding-top")); - if (self.speed > 1) { - var inner_offset = - self.$target.outerHeight() + this.height / this.width * document.body.clientWidth; - var outer_offset = self.$target.offset().top - (document.body.clientHeight - self.$target.outerHeight()) - padding; - offset = - outer_offset * self.speed + inner_offset; - } else { - offset = - self.$target.offset().top * self.speed; - } - self.offset = offset > 0 ? 0 : offset; - self.on_scroll(); - }; - img.src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)/g, ""); + // Provide a "safe-area" to limit parallax + this.$bg.css({ + top: -this.ratio, + bottom: -this.ratio + }); }, + on_scroll: function (scrollOffset) { + if (this.speed === 0 || this.speed === 1) return; - on_scroll: function () { - if (this.speed === 1) return; - var top = this.offset + window.scrollY * this.speed; - this.$target.css("background-position", "0px " + top + "px"); - }, + // Perform translate if the element is visible only + var vpEndOffset = scrollOffset + this.viewport; + if (vpEndOffset >= this.visible_area[0] && vpEndOffset <= this.visible_area[1]) { + this.$bg.css("transform", "translateY(" + _getNormalizedPosition.call(this, vpEndOffset) + "px)"); + } + + function _getNormalizedPosition(pos) { + // Normalize scroll in a 1 to 0 range + var r = (pos - this.visible_area[1]) / (this.visible_area[0] - this.visible_area[1]); + // Normalize accordingly to current options + return Math.round(this.ratio * (2 * r - 1)); + } + } }); animation.registry.share = animation.Class.extend({ diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index 4fbb79dd89e..94bb408b9ac 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -299,34 +299,34 @@ options.registry.parallax = options.Class.extend({ this.grid.size = 8; return this.grid; }, - on_resize: function () { - this._refresh(); - }, - _refresh: function () { - this.$target.data("snippet-view").set_values(); - }, start: function () { this._super.apply(this, arguments); if (!this.$target.data("snippet-view")) { this.$target.data("snippet-view", new animation.registry.parallax(this.$target)); } - this.scroll(); - this._refresh_callback = this._refresh.bind(this); - this.buildingBlock.$el.on("snippet-dropped snippet-activated", this._refresh_callback); - this.$target.on('snippet-option-change snippet-option-preview', this._refresh_callback); + this._toggle_refresh_callback(true); + }, + on_focus: function () { + this._super.apply(this, arguments); + this._update_target_to_bg(); + }, + on_resize: function () { + this._super.apply(this, arguments); + this._refresh(); }, scroll: function (type, value) { this.$target.attr("data-scroll-background-ratio", value); this._refresh(); }, set_active: function () { + this._super.apply(this, arguments); this.$el.find('[data-scroll]').removeClass("active") .filter('[data-scroll="' + (this.$target.attr('data-scroll-background-ratio') || 0) + '"]').addClass("active"); }, clean_for_save: function () { this._super.apply(this, arguments); - this.$target.css("background-position", '').css("background-attachment", ''); + this._toggle_refresh_callback(false); }, on_move: function () { this._super.apply(this, arguments); @@ -334,8 +334,23 @@ options.registry.parallax = options.Class.extend({ }, on_remove: function () { this._super.apply(this, arguments); - this.$target.off("snippet-option-change snippet-option-preview", this._refresh_callback); - this.buildingBlock.$el.off("snippet-dropped snippet-activated", this._refresh_callback); + this._toggle_refresh_callback(false); + }, + _update_target_to_bg: function () { + this.editor.styles.background.$target = this.$target.data("snippet-view").$bg; + this.editor.styles.background.set_active(); + this.editor.styles.background_position.$target = this.$target.data("snippet-view").$bg; + this.editor.styles.background_position.set_active(); + }, + _refresh: function () { + _.defer((function () { + this.$target.data("snippet-view")._rebuild(); + this._update_target_to_bg(); + }).bind(this)); + }, + _toggle_refresh_callback: function (on) { + this.$target[on ? "on" : "off"]("snippet-option-change snippet-option-preview", this._refresh_callback); + this.buildingBlock.$el[on ? "on" : "off"]("snippet-dropped snippet-activated", this._refresh_callback); }, }); diff --git a/addons/website/static/src/less/website.snippets.less b/addons/website/static/src/less/website.snippets.less index 7b941acda2f..1a290e65767 100644 --- a/addons/website/static/src/less/website.snippets.less +++ b/addons/website/static/src/less/website.snippets.less @@ -402,6 +402,17 @@ div.carousel { margin-bottom: 32px; } } + + > .s_parallax_bg { + .o-position-absolute(0, 0, 0, 0); + background-size: cover; + background-attachment: scroll; + pointer-events: none; + z-index: -1; + } + &.s_parallax_is_fixed > .s_parallax_bg { + background-attachment: fixed; + } } .s_parallax { diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml index 82c308485e6..a1cbb1b4578 100644 --- a/addons/website/views/snippets.xml +++ b/addons/website/views/snippets.xml @@ -530,13 +530,13 @@