Skip to content

Feasibility: Animasjoner i Change- og ShowHide-operatorer

Dato: 2026-03-20 Relatert: Animation System V2 (add-animation-system branch) Relatert issue: #1804

Status (2026-05): Implementert. Analysen nedenfor er historisk kontekst. Den faktiske implementasjonen valgte en annen retning enn "Strategi C (Deferred v-if)":

  • Visual blocks ble byttet fra v-if til v-show, som eliminerer race condition ved show-animasjoner (komponenten er alltid mountet og lytter på animation:showHide).
  • Hide-animasjoner spilles av mens elementet er synlig, deretter setter onComplete-callback display: none via showHideFunctions.hideElement().
  • Change-animasjoner bruker animation:change event med halvert duration: fade-out, callback (innholdsendring), fade-in.
  • Se ShowHide.ts, AnimationMixin.ts (handleShowHideAnimation, handleChangeAnimation) for implementasjonsdetaljer.

Bakgrunn

Animasjonssystemet V2 støtter i dag:

  • Blokk-animasjoner — direkte på blokker (fade, slide, scale, etc. ved load/hover/click/scroll)
  • Flow-triggered animasjonerOperatorAnimations-panelet lar conversation-operatorer (statement, answer, link, text_input, ar) trigge animasjoner på visuelle blokker

Operatorer som Change (ChangeText, ChangeImage, ChangeUrl, ChangeVideo) og ShowHide har i dag ingen animasjonsstøtte — endringene skjer øyeblikkelig uten noen visuell overgang.


1. Animasjoner i Change-operatorer

Hva er Change?

Change-operatorene endrer innholdet i en visuell blokk under kjøring:

  • ChangeText → bytter tekst i tekst/button/html-blokker
  • ChangeImage → bytter bilde i graphic-blokker
  • ChangeUrl → bytter URL i link/button-blokker
  • ChangeVideo → bytter video-URL
AF ChangeTextOp (op.properties.body.inputText.value + targetAbbrevOpName)
  → Composer remapData (comp.payload.text + comp.payload.targetAbbrevOpName)
  → CE funcBlocks.ChangeText → changeFunctions.changeText()
  → CE DataStore.overrides.value['t1']['changeText'] = 'ny tekst'
  → CE BlockMixin.blockWithOverrides (computed) → Vue re-rendrer blokken

Endringen skjer synkront — blokkens innhold oppdateres umiddelbart uten overgang.

Foreslått løsning

Legge til animationTriggers-data (identisk format som eksisterende OperatorAnimations) på Change-operatorer, slik at brukeren kan velge en animasjon som spilles av på målblokken når innholdet endres.

Eksempel: ChangeText bytter tekst i t1, og samtidig trigges en fade-animasjon på t1 → teksten "fader over" til nytt innhold.

Endringer per repo

Application-Frontend:

  1. Vise animasjonsikon på Change-operatorer:

    • Change-operatorer er IKKE i STYLESCOMPONENTLIST (i _temp_buildercomponents.ts), som styrer om animasjons- og stil-ikonene vises
    • Alternativ A: Legg Change-typer til STYLESCOMPONENTLIST — men da får de også stilpanelet, som gir lite mening
    • Alternativ B (anbefalt): Lag en separat ANIMATIONCOMPONENTLIST eller opCanHaveAnimations-computed i OperatorBase.vue som inkluderer Change-typer i tillegg til de eksisterende
    • Animasjonsikonet i OperatorBase.vue bruker da den nye listen
  2. Lagre animasjonsdata:

    • Samme JSON-blob som OperatorAnimations allerede bruker: op.properties.body.animationTrigger.value
    • OperatorAnimations.vue-panelet gjenbrukes direkte — trenger ingen ny komponent
    • Forskjell: triggerType er alltid appear (animasjonen spilles når Change-blokken prosesseres), så appear/click/hover-velgeren kan skjules eller default-settes
  3. Target-auto-fill:

    • Siden Change allerede har et targetAbbrevOpName, kan animasjonspanelet forhåndsutfylle target-blokken til å matche Change-målet
    • Brukeren kan fortsatt velge en annen blokk (f.eks. animere en naboblokk når teksten endres)

Creative-Composer:

  1. Remap animasjonsdata fra Change-operatorer:
    • remapData.ts parser allerede body.animationTrigger?.value for operatorer i STYLESCOMPONENTLIST
    • Trenger å utvide denne parsingen til å også gjelde Change-operatorer
    • Setter comp.animationTriggers på den remappede FlowComponent

Creative-Engine:

  1. Emitte animasjon etter Change:
    • I funcBlocks.ts, etter at change('changeText', ...) er kalt, sjekk componentArray[0].animationTriggers
    • Hvis den finnes, emit animation:flow event via DataStore.emitter — identisk mønster som conversationFlow.emitAnimationTriggers()
    • Animasjonen spilles av på målblokken via AnimationMixin.handleFlowAnimation()

Kompleksitet

OmrådeEstimatKommentar
AF: Utvidelse av animasjonsikon-gateLavNy liste eller computed, ~10 linjer
AF: OperatorAnimations gjenbrukIngenPanelet fungerer allerede
Composer: Remap-utvidelseLavUtvide eksisterende if-sjekk, ~5 linjer
CE: Emit etter ChangeLav~15 linjer i funcBlocks.ts
TotaltLavFølger eksisterende mønster 1:1

Begrensninger

  • Animasjonen starter ETTER innholdet er endret — det er ikke en "crossfade" mellom gammelt og nytt innhold. For ekte crossfade trengs det å snapshot det gamle innholdet, noe som er vesentlig mer komplekst.
  • For ChangeImage: bilder laster asynkront, så animasjonen kan starte før det nye bildet er rendret. En preload-mekanisme ville løst dette, men er ikke nødvendig for MVP.

2. Animasjoner i ShowHide-operatorer

Hva er ShowHide?

ShowHide viser eller skjuler blokker under kjøring. Brukeren velger flere targets med enten hide eller show action.

AF ShowHideOp (op.properties.body.targets = [{target:'b1-wrap', action:'hide'}, ...])
  → Composer remapData (comp.payload.targets = [...])
  → CE funcBlocks.ShowHide → ShowHide.init()
  → CE showHideFunctions.hideElement('b1-wrap')
  → CE DataStore.runtimeHidden.value['b1-wrap'] = true
  → CE BlockMixin.isRuntimeHidden → v-if="!isRuntimeHidden" → element fjernes fra DOM

Blokker vises/skjules øyeblikkelig via v-if. Ingen overgang.

Hovedutfordring: v-if vs v-show

De fleste visuelle blokker bruker v-if="!isRuntimeHidden", som fjerner elementet fra DOM. En animasjon kan ikke spilles av på et element som ikke eksisterer. For animert show/hide trengs en av disse:

StrategiFordelerUlemper
A: v-show + CSS-animasjonEnkel, elementet er alltid i DOMSkjulte blokker tar plass i layout (kan løses med height: 0; overflow: hidden)
B: Vue <Transition>Native Vue-løsning, håndterer inn/ut-animasjonerKrever omstrukturering av alle blokk-templates
C: Deferred v-ifBevarer nåværende oppførsel + animasjonMer kompleks: sett v-if=true, spill animasjon, vent på animationend, sett v-if=false

Foreslått løsning: Strategi C (Deferred v-if) med animasjons-events

Konsept: Behold v-if som primær mekanisme, men legg til et mellomsteg for animert skjuling:

Show (vise blokk):

  1. DataStore.runtimeHidden.value['b1-wrap'] = false (blokken mountes i DOM)
  2. Emit animation:show event til blokken
  3. AnimationMixin spiller inngangsanimasjon (fade-in, slide-in, etc.)

Hide (skjule blokk):

  1. Emit animation:hide event til blokken med animasjonsconfig
  2. AnimationMixin spiller utgangsanimasjon (fade-out, slide-out, etc.)
  3. Lytter på animationend event
  4. ETTER animasjonen: DataStore.runtimeHidden.value['b1-wrap'] = true (blokken unmountes fra DOM)

Endringer per repo

Application-Frontend:

  1. Utvide ShowHideOp.vue med animasjonsvalg:

    • Per target i targets[]-arrayet, legg til animasjonsconfig:
    typescript
    targets: [{
      target: 'b1-wrap',
      action: 'hide',
      animation?: {
        effects: AnimationEffect[]    // gjenbruk av eksisterende effekt-typer
        duration: number              // ms
        easing: string
      }
    }]
    • En kompakt animasjonsvelger per target-rad — f.eks. en preset-dropdown (fade, slide, scale) og en duration-input
    • Alternativt: "Advanced" toggle som åpner full effekt-editor (som AnimationConfigCard)
  2. Forenklet animasjonsvelger (anbefalt for ShowHide):

    • Ikke fullt AnimationConfigCard — for overdrevet for show/hide
    • Heller en enkel rad med: [Animasjon: None ▾] [Varighet: 300ms]
    • Preset-dropdown: None, Fade, Slide Up, Slide Down, Slide Left, Slide Right, Scale, Blur
    • Mappet til forhåndsdefinerte effects[]-arrays fra ANIMATION_PRESETS
    • For avanserte brukere: en "Custom..." valg som åpner full effekt-editor

Creative-Composer:

  1. Utvide remap for ShowHide:
    • Parse animation-objektet fra hver target og inkluder det i comp.payload.targets
    • Ingen ny type trengs — det er bare ekstra felter på eksisterende targets

Creative-Engine:

  1. Utvide ShowHide.ts:

    typescript
    // Pseudo-kode
    targets.forEach(({ target, action, animation }) => {
      if (action === 'show') {
        showHideFunctions.showElement(target)
        if (animation) {
          DataStore.emitter.emit('animation:show', {
            targetBlock: target,
            effects: animation.effects,
            duration: animation.duration,
            easing: animation.easing
          })
        }
      } else if (action === 'hide') {
        if (animation) {
          DataStore.emitter.emit('animation:hide', {
            targetBlock: target,
            effects: animation.effects,
            duration: animation.duration,
            easing: animation.easing,
            callback: () => showHideFunctions.hideElement(target)
          })
        } else {
          showHideFunctions.hideElement(target)
        }
      }
    })
  2. Utvide AnimationMixin.ts for show/hide:

    • Lytt på animation:show → spill inngangsanimasjon (normalt)
    • Lytt på animation:hide → spill utgangsanimasjon (reversed), vent animationend, kall callback
    • Viktig for hide: Callback-mønsteret sikrer at runtimeHidden settes ETTER animasjonen er ferdig
    • Trenger timeout-fallback i tilfelle animationend ikke fyrer (f.eks. element allerede skjult)
  3. Rapid toggle-håndtering:

    • Hvis brukeren skjuler og viser raskt: avbryt pågående hide-animasjon, start show-animasjon
    • Bruk en pendingHideTimeout som slettes ved ny show-event
    • animation.cancel() (Web Animations API) eller fjern CSS-klasse for å avbryte

Kompleksitet

OmrådeEstimatKommentar
AF: ShowHideOp animasjonsvelgerMediumNy UI i eksisterende operator, ~80 linjer
AF: Preset-mapping for show/hideLavGjenbruk ANIMATION_PRESETS
Composer: Utvide target-remapLavEkstra felt, ~10 linjer
CE: ShowHide.ts med eventsLav-MediumDeferred hide-mønsteret, ~40 linjer
CE: AnimationMixin show/hideMediumNy event-handler, callback, cancel-logikk, ~60 linjer
CE: Rapid toggle edge casesMediumMå teste grundig
TotaltMediumHovedkompleksiteten ligger i deferred hide + edge cases

Begrensninger og edge cases

  1. Layout shift: Når en blokk fader ut og deretter fjernes fra DOM (v-if=false), vil innholdet under den "hoppe opp". For å unngå dette kan man:

    • Sette visibility: hidden; height: 0; overflow: hidden i stedet for display: none etter animasjonen
    • Eller akseptere layout-shift (ofte ønskelig — plassen frigjøres etter blokkfjerning)
  2. Conversation-operatorer i ShowHide: ShowHide kan også skjule conversation-elementer (statements, answers). Disse rendres i MessageHolder.vue, ikke som visuelle blokker. Animasjon for disse er vanskeligere fordi de har egen scroll/rendering-logikk.

  3. Initielt skjulte blokker: Blokker som starter som skjult (runtimeHidden = true fra start) mountes aldri, så show-animasjonen trigges når de mountes for første gang. v-if sikrer at de ikke eksisterer i DOM — de må mountes FØRST, deretter animeres. Rekkefølge: fjern runtimeHidden → Vue mounter → $nextTick → spill animasjon.


3. Sammendrag og anbefaling

FeatureFeasibilityVerdi for brukerAnbefaling
Change + animasjonHøy (lav kompleksitet)Høy — innholdsendringer med visuell overgang er polertGjør dette først
ShowHide show-animasjonHøyHøy — blokker som fader inn er mye bedre enn pop-inGjør dette sammen med Change
ShowHide hide-animasjonMediumMedium-Høy — avhenger av brukscaseGjør dette etter show er ferdig

Prioritert rekkefølge

  1. Change-animasjoner — lavthengende frukt, gjenbruker alt eksisterende
  2. ShowHide show-animasjoner — enkel retning (element mountes, deretter animeres)
  3. ShowHide hide-animasjoner — deferred-pattern med callback krever mer testing

Alternativ: Forenklet tilnærming for V1

Dropp deferred hide-animasjon helt i V1. Gi brukeren:

  • Change: animasjon på målblokken når innholdet endres
  • ShowHide show: animasjon når blokken vises
  • ShowHide hide: øyeblikkelig (som i dag)

Dette halverer kompleksiteten og dekker de vanligste brukscasene. Hide-animasjon kan legges til i V2.


Nøkkelfiler

FilFormål
AF: CavaiFlow/operators/ShowHideOp.vueShowHide operator UI
AF: CavaiFlow/operators/ChangeTextOp.vueChangeText operator UI
AF: CavaiFlow/flowactors/OperatorBase.vueOperator-shell, styrer ikoner
AF: CavaiFlow/flowactors/OperatorAnimations.vueAnimasjonspanel (gjenbrukes for Change)
AF: utils/_temp_buildercomponents.tsSTYLESCOMPONENTLIST — gate for ikoner
Composer: remapper/remapData.tsRemapper AF→CE data
CE: components/blocks/functional/ShowHide.tsShowHide prosessering
CE: components/blocks/functional/Script.tsChange-funksjoner, setOverride
CE: utils/funcBlocks.tsFunksjonell blokk-routing
CE: mixins/AnimationMixin.tsAnimasjonslogikk på blokker
CE: mixins/BlockMixin.tsisRuntimeHidden, blockWithOverrides
CE: services/dataStore.tsruntimeHidden, overrides

Internal documentation