From 3c09c86da0f431fe051084fc9b0559d162e28ee4 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Thu, 2 Nov 2023 07:03:11 +0100 Subject: [PATCH] Add heading entrance animations REDMINE-20488 --- .../locales/new/heading_subtitles.de.yml | 12 +++++ .../locales/new/heading_subtitles.en.yml | 12 +++++ .../src/contentElements/heading/Heading.js | 46 ++++++++++++++++++- .../heading/Heading.module.css | 42 +++++++++++++++++ .../src/contentElements/heading/editor.js | 3 ++ .../src/contentElements/heading/frontend.js | 3 +- 6 files changed, 115 insertions(+), 3 deletions(-) diff --git a/entry_types/scrolled/config/locales/new/heading_subtitles.de.yml b/entry_types/scrolled/config/locales/new/heading_subtitles.de.yml index af51ec3c90..e346b2513f 100644 --- a/entry_types/scrolled/config/locales/new/heading_subtitles.de.yml +++ b/entry_types/scrolled/config/locales/new/heading_subtitles.de.yml @@ -3,3 +3,15 @@ de: inline_editing: type_subtitle: "Untertitel eingeben" type_tagline: "Tagline eingeben" + editor: + content_elements: + heading: + attributes: + entranceAnimation: + inline_help: Lege die Animation fest, mit der die Überschrift eingeblendet werden soll, wenn sie die Mitte des Viewports erreicht. + label: Eingangsanimation + values: + none: "(Keine)" + fadeIn: Einblenden + fadeInFast: Einblenden (Schnell) + fadeInSlow: Einblenden (Langsam) diff --git a/entry_types/scrolled/config/locales/new/heading_subtitles.en.yml b/entry_types/scrolled/config/locales/new/heading_subtitles.en.yml index b64ecfeac3..66b2170840 100644 --- a/entry_types/scrolled/config/locales/new/heading_subtitles.en.yml +++ b/entry_types/scrolled/config/locales/new/heading_subtitles.en.yml @@ -3,3 +3,15 @@ en: inline_editing: type_subtitle: "Type subtitle" type_tagline: "Type tagline" + editor: + content_elements: + heading: + attributes: + entranceAnimation: + inline_help: Determine how the heading becomes visible once it reaches the center of the viewport + label: Entrance Animation + values: + fadeIn: Fade in + fadeInFast: Fade in (Fast) + fadeInSlow: Fade in (Slow) + none: "(None)" diff --git a/entry_types/scrolled/package/src/contentElements/heading/Heading.js b/entry_types/scrolled/package/src/contentElements/heading/Heading.js index 7592d17b8c..2da9323405 100644 --- a/entry_types/scrolled/package/src/contentElements/heading/Heading.js +++ b/entry_types/scrolled/package/src/contentElements/heading/Heading.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect, useState, useRef} from 'react'; import classNames from 'classnames'; import { withShadowClassName, @@ -7,7 +7,9 @@ import { EditableInlineText, useContentElementConfigurationUpdate, useContentElementEditorState, + useContentElementLifecycle, useDarkBackground, + useIsStaticPreview, useI18n, contentElementWidths, utils @@ -21,13 +23,48 @@ export function Heading({configuration, sectionProps, contentElementWidth}) { const updateConfiguration = useContentElementConfigurationUpdate(); const {t} = useI18n({locale: 'ui'}); const darkBackground = useDarkBackground(); - const {isSelected} = useContentElementEditorState(); + const {isSelected, isEditable} = useContentElementEditorState(); const legacyValue = configuration.children; const Tag = firstSectionInEntry ? 'h1' : 'h2'; const forcePaddingTop = firstSectionInEntry && !('marginTop' in configuration); + const entranceAnimation = (!useIsStaticPreview() && configuration.entranceAnimation) || 'none'; + const [animating, setAnimating] = useState(false); + + useContentElementLifecycle({ + onActivate() { + setAnimating(entranceAnimation !== 'none'); + }, + + onInvisible() { + if (isEditable) { + setAnimating(false); + } + } + }); + + const previousAnimation = useRef(entranceAnimation); + const previouslySelected = useRef(isSelected); + + useEffect(() => { + if (isEditable && previousAnimation.current !== entranceAnimation) { + previousAnimation.current = entranceAnimation; + + setAnimating(false) + setTimeout(() => setAnimating(true), 100); + } + }, [entranceAnimation, isEditable]); + + useEffect(() => { + if (!previouslySelected.current && isSelected) { + setAnimating(true) + } + + previouslySelected.current = isSelected; + }, [isSelected]); + function renderSubtitle(name) { const value = configuration[name]; @@ -50,6 +87,11 @@ export function Heading({configuration, sectionProps, contentElementWidth}) { return (
contentElementWidths.md || diff --git a/entry_types/scrolled/package/src/contentElements/heading/Heading.module.css b/entry_types/scrolled/package/src/contentElements/heading/Heading.module.css index cbda6351e8..6fc1996cb9 100644 --- a/entry_types/scrolled/package/src/contentElements/heading/Heading.module.css +++ b/entry_types/scrolled/package/src/contentElements/heading/Heading.module.css @@ -9,6 +9,48 @@ padding-top: 0.45em; } +.animation-fadeIn { + --fade-in-duration: 2s; + --fade-in-delay: 0.4s; +} + +.animation-fadeInFast { + composes: animation-fadeIn; + --fade-in-duration: 1s; + --fade-in-delay: 0.4s; +} + +.animation-fadeInSlow { + composes: animation-fadeIn; + --fade-in-duration: 3s; + --fade-in-delay: 0.8s; +} + +.animation-fadeIn .main, +.animation-fadeIn .tagline, +.animation-fadeIn .subtitle { + opacity: 0; +} + +.animation-fadeIn.animating .main, +.animation-fadeIn.animating .tagline, +.animation-fadeIn.animating .subtitle { + transition: opacity var(--fade-in-duration) ease; + opacity: 1; +} + +.animation-fadeIn.animating .subtitle { + transition-delay: var(--fade-in-delay); +} + +.animation-fadeIn.hasTagline.animating .main { + transition-delay: var(--fade-in-delay); +} + +.animation-fadeIn.hasTagline.animating .subtitle { + transition-delay: calc(var(--fade-in-delay) * 2); +} + .main { margin: 0; } diff --git a/entry_types/scrolled/package/src/contentElements/heading/editor.js b/entry_types/scrolled/package/src/contentElements/heading/editor.js index 3e01bdfdca..8deee29dec 100644 --- a/entry_types/scrolled/package/src/contentElements/heading/editor.js +++ b/entry_types/scrolled/package/src/contentElements/heading/editor.js @@ -40,6 +40,9 @@ editor.contentElementTypes.register('heading', { model: modelDelegator, propertyName: 'color' }); + this.input('entranceAnimation', SelectInputView, { + values: ['none', 'fadeInSlow', 'fadeIn', 'fadeInFast'], + }); this.input('hyphens', SelectInputView, { values: ['auto', 'manual'] diff --git a/entry_types/scrolled/package/src/contentElements/heading/frontend.js b/entry_types/scrolled/package/src/contentElements/heading/frontend.js index 6d1ceaaf1f..f5a927271c 100644 --- a/entry_types/scrolled/package/src/contentElements/heading/frontend.js +++ b/entry_types/scrolled/package/src/contentElements/heading/frontend.js @@ -2,5 +2,6 @@ import {frontend} from 'pageflow-scrolled/frontend'; import {Heading} from './Heading'; frontend.contentElementTypes.register('heading', { - component: Heading + component: Heading, + lifecycle: true });