summaryrefslogtreecommitdiff
path: root/addons/website/static/src
diff options
context:
space:
mode:
authorBenjamin Vray <bvr@odoo.com>2019-11-06 15:01:07 +0000
committerqsm-odoo <qsm@odoo.com>2020-03-31 18:16:08 +0000
commit3defe35a0e4e2097115633797df6898ac2724c66 (patch)
treef20fb822b258fde0fefa59045889944d6f5dba6d /addons/website/static/src
parent5ba817c453270fa8366397f9767d79072054df20 (diff)
[IMP] website, *: add and review header visibility options
*: portal Adding a choice of several header behaviors (fixed, fade out with scroll, etc.). Review the way those animations works in mobile/desktop and now perform them without duplicating the header DOM element. Also allow to hide of adapt the size of the logo when the page is scrolled. Part of https://github.com/odoo/odoo/pull/38950 task-2087641 Co-authored-by: qsm-odoo <qsm@odoo.com>
Diffstat (limited to 'addons/website/static/src')
-rw-r--r--addons/website/static/src/js/content/menu.js344
-rw-r--r--addons/website/static/src/scss/secondary_variables.scss1
-rw-r--r--addons/website/static/src/scss/website.edit_mode.scss7
-rw-r--r--addons/website/static/src/scss/website.scss78
4 files changed, 353 insertions, 77 deletions
diff --git a/addons/website/static/src/js/content/menu.js b/addons/website/static/src/js/content/menu.js
index 611ed40b554..36caa0accbb 100644
--- a/addons/website/static/src/js/content/menu.js
+++ b/addons/website/static/src/js/content/menu.js
@@ -1,66 +1,66 @@
odoo.define('website.content.menu', function (require) {
'use strict';
+const config = require('web.config');
var dom = require('web.dom');
var publicWidget = require('web.public.widget');
var wUtils = require('website.utils');
+var animations = require('website.content.snippets.animation');
-publicWidget.registry.affixMenu = publicWidget.Widget.extend({
- selector: 'header.o_affix_enabled',
+const BaseAnimatedHeader = animations.Animation.extend({
+ disabledInEditableMode: false,
+ effects: [{
+ startEvents: 'scroll',
+ update: '_updateHeaderOnScroll',
+ }, {
+ startEvents: 'resize',
+ update: '_updateHeaderOnResize',
+ }],
/**
+ * @constructor
+ */
+ init: function () {
+ this._super(...arguments);
+ this.fixedHeader = false;
+ this.scrolledPoint = 0;
+ this.hasScrolled = false;
+ },
+ /**
* @override
*/
start: function () {
- var def = this._super.apply(this, arguments);
-
- var self = this;
- this.$headerClone = this.$target.clone().addClass('o_header_affix affix o_top_fixed_element').removeClass('o_affix_enabled').removeAttr('id');
- this.$headerClone.insertAfter(this.$target);
- this.$headers = this.$target.add(this.$headerClone);
- this.$dropdowns = this.$headers.find('.dropdown');
- this.$dropdownMenus = this.$headers.find('.dropdown-menu');
- this.$navbarCollapses = this.$headers.find('.navbar-collapse');
-
- this._adaptDefaultOffset();
- wUtils.onceAllImagesLoaded(this.$headerClone).then(function () {
- self._adaptDefaultOffset();
- });
+ this.$main = this.$el.next('main');
+ this.isOverlayHeader = !!this.$el.closest('.o_header_overlay, .o_header_overlay_theme').length;
+ this.$dropdowns = this.$el.find('.dropdown, .dropdown-menu');
+ this.$navbarCollapses = this.$el.find('.navbar-collapse');
- // Handle events for the collapse menus
- _.each(this.$headerClone.find('[data-toggle="collapse"]'), function (el) {
- var $source = $(el);
- var targetIDSelector = $source.attr('data-target');
- var $target = self.$headerClone.find(targetIDSelector);
- $source.attr('data-target', targetIDSelector + '_clone');
- $target.attr('id', targetIDSelector.substr(1) + '_clone');
- });
- // While scrolling through navbar menus, body should not be scrolled with it
- this.$headerClone.find('div.navbar-collapse').on('show.bs.collapse', function () {
- $(document.body).addClass('overflow-hidden');
- }).on('hide.bs.collapse', function () {
+ // While scrolling through navbar menus on medium devices, body should not be scrolled with it
+ this.$navbarCollapses.on('show.bs.collapse.BaseAnimatedHeader', function () {
+ if (config.device.size_class <= config.device.SIZES.SM) {
+ $(document.body).addClass('overflow-hidden');
+ }
+ }).on('hide.bs.collapse.BaseAnimatedHeader', function () {
$(document.body).removeClass('overflow-hidden');
});
- // Window Handlers
- $(window).on('resize.affixMenu scroll.affixMenu', _.throttle(this._onWindowUpdate.bind(this), 200));
- setTimeout(this._onWindowUpdate.bind(this), 0); // setTimeout to allow override with advanced stuff... see themes
+ // We can rely on transitionend which is well supported but not on
+ // transitionstart, so we listen to a custom odoo event.
+ this._transitionCount = 0;
+ this.$el.on('odoo-transitionstart.BaseAnimatedHeader', () => this._updateMainPaddingTopLoop(1));
+ this.$el.on('transitionend.BaseAnimatedHeader', () => this._updateMainPaddingTopLoop(-1));
- return def.then(function () {
- self.trigger_up('widgets_start_request', {
- $target: self.$headerClone,
- });
- });
+ return this._super(...arguments);
},
/**
* @override
*/
destroy: function () {
- if (this.$headerClone) {
- this.$headerClone.remove();
- $(window).off('.affixMenu');
- }
- this._super.apply(this, arguments);
+ this._toggleFixedHeader(false);
+ this.$el.removeClass('o_header_affixed o_header_is_scrolled o_header_no_transition');
+ this.$navbarCollapses.off('.BaseAnimatedHeader');
+ this.$el.off('.BaseAnimatedHeader');
+ this._super(...arguments);
},
//--------------------------------------------------------------------------
@@ -69,16 +69,41 @@ publicWidget.registry.affixMenu = publicWidget.Widget.extend({
/**
* @private
+ * @param {boolean} [useFixed=true]
*/
- _adaptDefaultOffset: function () {
- var bottom = this.$target.offset().top + this._getHeaderHeight();
- this.$headerClone.css('margin-top', Math.min(-200, -bottom) + 'px');
+ _toggleFixedHeader: function (useFixed = true) {
+ this.fixedHeader = useFixed;
+ this.el.classList.toggle('o_header_affixed', useFixed);
+ this._updateMainPaddingTop();
},
/**
* @private
*/
- _getHeaderHeight: function () {
- return this.$headerClone.outerHeight();
+ _updateMainPaddingTop: function () {
+ this.headerHeight = this.$el.outerHeight();
+ if (this.isOverlayHeader) {
+ return;
+ }
+ const headerSize = this.el.classList.contains('o_header_affixed');
+ this.$main.css('padding-top', headerSize ? this.headerHeight : '');
+ },
+ /**
+ * @private
+ * @param {integer} [addCount=0]
+ */
+ _updateMainPaddingTopLoop: function (addCount = 0) {
+ this._updateMainPaddingTop();
+
+ this._transitionCount += addCount;
+ this._transitionCount = Math.max(0, this._transitionCount);
+
+ // The normal case would be to have the transitionend event to be
+ // fired but we cannot rely on it, so we use a timeout as fallback.
+ clearTimeout(this._paddingLoopTimer);
+ if (this._transitionCount > 0) {
+ window.requestAnimationFrame(() => this._updateMainPaddingTopLoop());
+ this._paddingLoopTimer = setTimeout(() => this._updateMainPaddingTopLoop(-1), 500);
+ }
},
//--------------------------------------------------------------------------
@@ -86,31 +111,232 @@ publicWidget.registry.affixMenu = publicWidget.Widget.extend({
//--------------------------------------------------------------------------
/**
- * Called when the window is resized or scrolled -> updates affix status and
- * automatically closes submenus.
+ * Called when the window is scrolled
*
* @private
+ * @param {integer} scroll
*/
- _onWindowUpdate: function () {
- if (this.$navbarCollapses.hasClass('show')) {
- return;
+ _updateHeaderOnScroll: function (scroll) {
+ // Disable css transition if refresh with scrollTop > 0
+ if (!this.hasScrolled) {
+ this.hasScrolled = true;
+ if (scroll > 0) {
+ this.$el.addClass('o_header_no_transition');
+ }
+ } else {
+ this.$el.removeClass('o_header_no_transition');
}
- var wOffset = $(window).scrollTop();
- var hOffset = this.$target.scrollTop();
- this.$headerClone.toggleClass('affixed', wOffset > (hOffset + 300));
+ // Indicates the page is scrolled, the logo size is changed.
+ const headerIsScrolled = (scroll > this.scrolledPoint);
+ if (this.headerIsScrolled !== headerIsScrolled) {
+ this.el.classList.toggle('o_header_is_scrolled', headerIsScrolled);
+ this.$el.trigger('odoo-transitionstart');
+ this.headerIsScrolled = headerIsScrolled;
+ }
// Reset opened menus
- this.$dropdowns.add(this.$dropdownMenus).removeClass('show');
+ if (this.$navbarCollapses.hasClass('show')) {
+ return;
+ }
+ this.$dropdowns.removeClass('show');
this.$navbarCollapses.removeClass('show').attr('aria-expanded', false);
},
+ /**
+ * Called when the window is resized
+ *
+ * @private
+ */
+ _updateHeaderOnResize: function () {
+ if (document.body.classList.contains('overflow-hidden')
+ && config.device.size_class > config.device.SIZES.SM) {
+ document.body.classList.remove('overflow-hidden');
+ this.$el.find('.navbar-collapse').removeClass('show');
+ }
+ },
+});
+
+publicWidget.registry.StandardAffixedHeader = BaseAnimatedHeader.extend({
+ selector: 'header.o_header_standard:not(.o_header_sidebar)',
+
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super(...arguments);
+ this.fixedHeaderShow = false;
+ this.scrolledPoint = 300;
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ this.headerHeight = this.$el.outerHeight();
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when the window is scrolled
+ *
+ * @private
+ * @param {integer} scroll
+ */
+ _updateHeaderOnScroll: function (scroll) {
+ this._super(...arguments);
+
+ const mainPosScrolled = (scroll > this.headerHeight);
+ const reachPosScrolled = (scroll > this.scrolledPoint);
+
+ // Switch between static/fixed position of the header
+ if (this.fixedHeader !== mainPosScrolled) {
+ this.$el.css('transform', mainPosScrolled ? 'translate(0, -100%)' : '');
+ void this.$el[0].offsetWidth; // Force a paint refresh
+ this._toggleFixedHeader(mainPosScrolled);
+ }
+ // Show/hide header
+ if (this.fixedHeaderShow !== reachPosScrolled) {
+ this.$el.css('transform', reachPosScrolled ? '' : 'translate(0, -100%)');
+ this.fixedHeaderShow = reachPosScrolled;
+ }
+ },
+});
+
+publicWidget.registry.FixedHeader = BaseAnimatedHeader.extend({
+ selector: 'header.o_header_fixed',
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _updateHeaderOnScroll: function (scroll) {
+ this._super(...arguments);
+ // Need to be 'unfixed' when the window is not scrolled so that the
+ // transparent menu option still works.
+ if (scroll > this.scrolledPoint) {
+ if (!this.$el.hasClass('o_header_affixed')) {
+ this._toggleFixedHeader(true);
+ }
+ } else {
+ this._toggleFixedHeader(false);
+ }
+ },
+});
+
+const BaseDisappearingHeader = publicWidget.registry.FixedHeader.extend({
+ /**
+ * @override
+ */
+ init: function () {
+ this._super(...arguments);
+ this.scrollingDownwards = true;
+ this.hiddenHeader = false;
+ this.position = 0;
+ this.checkPoint = 0;
+ },
+ /**
+ * @override
+ */
+ destroy: function () {
+ this._showHeader();
+ this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @abstract
+ */
+ _hideHeader: function () {},
+ /**
+ * @private
+ */
+ _showHeader: function () {},
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _updateHeaderOnScroll: function (scroll) {
+ this._super(...arguments);
+
+ const scrollingDownwards = (scroll > this.position);
+ if (scrollingDownwards !== this.scrollingDownwards) {
+ this.checkPoint = scroll;
+ }
+
+ if (scrollingDownwards) {
+ if (!this.hiddenHeader && scroll - this.checkPoint > 200) {
+ this.hiddenHeader = true;
+ this._hideHeader();
+ }
+ } else {
+ if (this.hiddenHeader && scroll - this.checkPoint < -100) {
+ this.hiddenHeader = false;
+ this._showHeader();
+ }
+ }
+
+ this.scrollingDownwards = scrollingDownwards;
+ this.position = scroll;
+ },
+});
+
+publicWidget.registry.DisappearingHeader = BaseDisappearingHeader.extend({
+ selector: 'header.o_header_disappears',
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _hideHeader: function () {
+ this.$el.css('transform', 'translate(0, -100%)');
+ },
+ /**
+ * @override
+ */
+ _showHeader: function () {
+ this.$el.css('transform', '');
+ },
+});
+
+publicWidget.registry.FadeOutHeader = BaseDisappearingHeader.extend({
+ selector: 'header.o_header_fade_out',
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _hideHeader: function () {
+ this.$el.stop(false, true).fadeOut();
+ },
+ /**
+ * @override
+ */
+ _showHeader: function () {
+ this.$el.stop(false, true).fadeIn();
+ },
});
/**
* Auto adapt the header layout so that elements are not wrapped on a new line.
- *
- * Note: this works well with the affixMenu... by chance (autohideMenu is called
- * after alphabetically).
*/
publicWidget.registry.autohideMenu = publicWidget.Widget.extend({
selector: 'header #top_menu',
@@ -225,7 +451,7 @@ publicWidget.registry.menuDirection = publicWidget.Widget.extend({
// If can't open in the current direction because it would overflow the
// window, change the direction. But if the other direction would do the
// same, change back the direction.
- for (var i = 0 ; i < 2 ; i++) {
+ for (var i = 0; i < 2; i++) {
if (!this._checkOpening(alignment, liOffset, liWidth, menuWidth, windowWidth)) {
alignment = (alignment === 'left' ? 'right' : 'left');
}
diff --git a/addons/website/static/src/scss/secondary_variables.scss b/addons/website/static/src/scss/secondary_variables.scss
index 5f3d6704c85..7eb722f34e8 100644
--- a/addons/website/static/src/scss/secondary_variables.scss
+++ b/addons/website/static/src/scss/secondary_variables.scss
@@ -77,6 +77,7 @@ $-with-support-theme-fonts: ();
$o-theme-fonts: $-with-support-theme-fonts;
$o-theme-navbar-logo-height: o-website-value('logo-height') !default;
+$o-theme-navbar-fixed-logo-height: o-website-value('fixed-logo-height') !default;
$o-theme-font: nth($o-theme-fonts, o-website-value('font-number')) !default;
$o-theme-headings-font: nth($o-theme-fonts, o-website-value('headings-font-number')) !default;
$o-theme-navbar-font: nth($o-theme-fonts, o-website-value('navbar-font-number')) !default;
diff --git a/addons/website/static/src/scss/website.edit_mode.scss b/addons/website/static/src/scss/website.edit_mode.scss
index 495873955fd..c076b050fd0 100644
--- a/addons/website/static/src/scss/website.edit_mode.scss
+++ b/addons/website/static/src/scss/website.edit_mode.scss
@@ -161,3 +161,10 @@ table.editorbar-panel {
}
}
}
+
+.editor_has_snippets {
+ .o_header_affixed {
+ left: $o-we-sidebar-width !important;
+ top: var(--o-we-toolbar-height) !important;
+ }
+}
diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss
index 6c76dac6179..cc7af6543bd 100644
--- a/addons/website/static/src/scss/website.scss
+++ b/addons/website/static/src/scss/website.scss
@@ -55,6 +55,7 @@ $-font-numbers: (
--h6: #{$-h6-color};
--logo-height: #{$o-theme-navbar-logo-height};
+ --fixed-logo-height: #{$o-theme-navbar-fixed-logo-height};
--number-of-fonts: #{length($o-theme-fonts)};
}
@@ -643,28 +644,50 @@ table.table_desc tr td {
// Layout
//
-// Header
-.o_header_affix {
+$-transition-duration: 200ms;
+
+// Affixed Header
+.o_header_affixed {
display: block;
@include o-position-absolute(0, 0, auto, 0);
position: fixed;
z-index: $zindex-modal - 20;
background: $light;
- margin-top: -999px;
- transition: margin-top 500ms ease 0s;
- @include media-breakpoint-down(sm) {
- .navbar-collapse {
- @if o-website-value('header-template') != 'logo_hamburger' {
- max-height: 70vh;
- }
+
+ &:not(.o_header_no_transition) {
+ transition: transform $-transition-duration;
+ }
+
+ .navbar-collapse {
+ @if o-website-value('header-template') != 'logo_hamburger' {
+ max-height: 70vh;
+ }
+ &.show {
overflow-y: auto;
}
}
- &.affixed {
- margin-top: 0px !important; // the default margin-top is adapted in JS
+ &.o_header_is_scrolled {
+ .navbar-brand {
+ font-size: $o-theme-navbar-fixed-logo-height / $line-height-base;
+
+ img {
+ height: $o-theme-navbar-fixed-logo-height;
+ }
+ }
+ @include media-breakpoint-up(md) {
+ @if (index(('menu_logo_below', 'logo_menu_below'), o-website-value('header-template'))) != null {
+ .navbar-brand {
+ font-size: 0px;
+ opacity: 0;
+ img {
+ height: 0px;
+ }
+ }
+ }
+ }
}
}
-#oe_main_menu_navbar + #wrapwrap .o_header_affix {
+#oe_main_menu_navbar + #wrapwrap .o_header_affixed {
top: $o-navbar-height;
}
@@ -694,6 +717,17 @@ table.table_desc tr td {
// Header templates
header {
+ &:not(.o_header_no_transition) {
+ .navbar-brand {
+ transition: font-size $-transition-duration, opacity $-transition-duration ease-out;
+
+ img {
+ transition: height $-transition-duration;
+ }
+ }
+ }
+
+ z-index: 1;
@include media-breakpoint-up(md) {
// Allow to center the logo, ignoring the toggler space
.o_navbar_toggler_container {
@@ -715,14 +749,20 @@ header {
.o_navbar_toggler_container {
direction: rtl;
}
- .container {
+ &:not(.o_header_is_scrolled) .container {
padding-top: $spacer;
padding-bottom: $spacer;
+ }
+ .container {
+ transition: padding 200ms;
#top_menu.navbar-nav {
height: 90vh;
padding-top : 10vh;
}
+ .dropdown-menu {
+ text-align: center;
+ }
}
}
} @else if index(('logo_menu_below', 'menu_logo_below'), o-website-value('header-template')) {
@@ -741,10 +781,12 @@ header {
margin: auto;
}
.navbar-brand {
- @if o-website-value('header-template') == 'menu_logo_below' {
- margin-top: $navbar-padding-y;
- } @else {
- margin-bottom: $navbar-padding-y;
+ @include media-breakpoint-up(md) {
+ @if o-website-value('header-template') == 'menu_logo_below' {
+ margin-top: $navbar-padding-y;
+ } @else {
+ margin-bottom: $navbar-padding-y;
+ }
}
}
@if o-website-value('header-template') == 'menu_logo_below' {
@@ -788,7 +830,7 @@ header {
}
#wrapwrap.o_header_overlay {
- > header:not(.o_header_affix) {
+ > header:not(.o_header_affixed) {
@include o-position-absolute(0, 0, auto, 0);
z-index: 1000;