// External dependencies import debounce from 'lodash/debounce'; import endsWith from 'lodash/endsWith'; import filter from 'lodash/filter'; import forEach from 'lodash/forEach'; import get from 'lodash/get'; import groupBy from 'lodash/groupBy'; import isArray from 'lodash/isArray'; import isEmpty from 'lodash/isEmpty'; import isObject from 'lodash/isObject'; import map from 'lodash/map'; import size from 'lodash/size'; // Internal dependencies import * as DOM from '@frontend-builder/features/scroll-effects/fe/io/dom'; import { ETBuilderScrollEffects } from '@frontend-builder/features/scroll-effects'; import { convertTransforms } from '@frontend-builder/features/scroll-effects/vb/helpers'; import { fromObject } from '@frontend-builder/features/scroll-effects/entities/scroll-effect'; import ETBuilderOffsetsConst from '@frontend-builder/constants/et-builder-offsets-const'; import { Window } from '@frontend-builder/features/scroll-effects/stores/window'; import { top_window } from '@core/admin/js/frame-helpers'; import { getContentAreaSelector } from 'gutenberg/utils/selectors'; const isBlockEditor = $(top_window.document).find(getContentAreaSelector(top_window, true)).length > 0; const isBuilder = isObject(window.ET_Builder) && size(window.ET_Builder) > 1 && ! isBlockEditor; const getViewportWidth = () => jQuery(window).width(); const toItem = (effects, id) => ({ id, effects: (effects || []).map(fromObject) }); const getSelector = (step, id) => `body[data-scroll-step="${step}"] ${id}`; const scrollEffects = new ETBuilderScrollEffects(DOM, getSelector); let motionEffectsLoaded = false; const getMotionElementsFE = () => { const allMotionElements = window.et_pb_motion_elements || []; // We are on the real FE, so load all the motion elements. if (! isBuilder || isEmpty(allMotionElements)) { return allMotionElements; } /** * We're inside the Builder, but still need to render motion effects from the TB * Get all the motion elements for TB parts of the layout. */ const breakpoints = ['desktop', 'tablet', 'phone']; const tb_motion_elements = {}; forEach(breakpoints, breakpoint => { tb_motion_elements[breakpoint] = filter(window.et_pb_motion_elements[breakpoint], el => (endsWith(el.id, '_tb_header') || endsWith(el.id, '_tb_body') || endsWith(el.id, '_tb_footer'))); }); return tb_motion_elements; }; const toggleMotionElements = (state = 'hide') => { const activeMotionElements = getMotionElementsFE(); const elementsList = get(activeMotionElements, 'desktop', []); if (isEmpty(elementsList)) { return; } forEach(elementsList, element => { const $element = jQuery(element.id); if ('hide' === state) { $element.addClass('et-pb-before-scroll-animation'); } else { $element.removeClass('et-pb-before-scroll-animation et_animated et-waypoint'); $element.addClass('et_had_animation'); } }); }; /** * @param {number} width * @returns {string} */ const getDevice = width => { if (width <= ETBuilderOffsetsConst.responsiveLandscape.phone) { return 'phone'; } if (width <= ETBuilderOffsetsConst.responsiveLandscape.tablet) { return 'tablet'; } return 'desktop'; }; const getChildren = (element, effects) => { const childrenCount = parseInt(element.children_count); const children = {}; if (childrenCount > 0) { for (let i = 0; i < childrenCount; i++) { const key = `.${element.module_type}_item_${element.module_index}_${i}`; children[key] = effects; } } return children; }; /** * @param {[object]} elements * @param {string} device */ const updateItems = (elements, device) => { const groupedElements = groupBy(elements[device] || [], 'id'); map(groupedElements, toItem) .forEach(({ id, effects }) => { if (! isEmpty(effects) && isArray(effects)) { const element = get(groupedElements, [id, '0'], {}); const start = get(element, 'trigger_start', 'middle'); const end = start; // Use only one offset point right now. Update the value here if/when we add second point. const motionTriggers = { start, end }; if ('on' === element.grid_motion) { if (! element.child_slug) { const children = getChildren(element, effects); forEach(children, (childModule, id) => { scrollEffects.add(id, childModule, motionTriggers); }); } } else { const transforms = isEmpty(element.transforms) ? [] : convertTransforms(element.transforms); scrollEffects.add(id, [...transforms, ...effects], motionTriggers); } } else { scrollEffects.remove(id); } }); if (! motionEffectsLoaded) { motionEffectsLoaded = true; setTimeout(() => toggleMotionElements('show'), 200); } }; const activeMotionElements = getMotionElementsFE(); if (! isEmpty(activeMotionElements)) { const $window = $(window); const windowStore = new Window($window.width(), $window.height()); const elements = activeMotionElements; let device = getDevice(getViewportWidth()); // Hide all the elements on page load so scroll effects applied smoothly toggleMotionElements(); jQuery(window).on('load', debounce(() => updateItems(elements, device), 500)); jQuery(window).on('resize', debounce(() => windowStore.setWidth($window.width()).setHeight($window.height()), 500)); windowStore.addSizeChangeListener(() => { const newDevice = getDevice(getViewportWidth()); if (newDevice !== device) { device = newDevice; updateItems(elements, device); } else { scrollEffects.refresh(); } }); }