Skip to content

Commit

Permalink
PickEmotion CSS Transitions -> Animation Loop
Browse files Browse the repository at this point in the history
The end result is different; not better, not worse, just different. The
code is a bit more complicated. The motivating reason for this change
is simple: there's no CSS transitions to use in native, so in order to
keep parity, I need to implement this without them!

This also incorporates another compatibility change: on native, theres
no convenient way to get the rendered size of components until a frame
*after* they successfully render. This doesn't go as far as delaying
checking the size until a frame later, but it does ensure the interface
is compatible with that and moves the size check to be after the
words are already mounted. Hence the passing WriteableValueWithCallbacks
to the Word component to get the size, rather then precomputing.
Same deal with the container size, since that depends on the word sizes
  • Loading branch information
Tjstretchalot committed Jun 22, 2023
1 parent b132e11 commit 5df1cc1
Show file tree
Hide file tree
Showing 3 changed files with 497 additions and 155 deletions.
63 changes: 48 additions & 15 deletions src/shared/anim/AnimationLoop.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { MutableRefObject, useCallback, useEffect, useMemo, useRef } from 'react';
import { Bezier } from '../lib/Bezier';
import {
BezierAnimation,
animIsComplete,
calculateAnimValue,
updateAnim,
} from '../lib/BezierAnimation';
import { BezierAnimation, animIsComplete, calculateAnimValue } from '../lib/BezierAnimation';
import { Callbacks } from '../lib/Callbacks';

export interface Animator<P extends object> {
Expand Down Expand Up @@ -73,6 +68,26 @@ export class TrivialAnimator<K extends string, T, P extends { [key in K]: T }>
reset(): void {}
}

type StdAnimatorOpts = {
/**
* What to do if the target changes while animating. We will need to
* recreate the animation easing, but the question comes with what to
* do with the duration: should we have the animation complete at the
* original time, or should it complete later?
*
* Generally this is not a subjective choice; if the target changes
* often and smoothly, replace is appropriate. Otherwise, extend is
* appropriate.
*
* @default true
*/
onTargetChange?: 'extend' | 'replace';
};

const defaultAnimatorOpts: Required<StdAnimatorOpts> = {
onTargetChange: 'extend',
};

/**
* Uses a Bezier animation curve for a single number field in the props.
* This animator will animate the field from the rendered value to the
Expand All @@ -87,19 +102,22 @@ export class BezierAnimator<P extends object> implements Animator<P> {
private readonly duration: number;
private readonly getter: (p: P) => number;
private readonly setter: (p: P, v: number) => void;
private readonly opts: Required<StdAnimatorOpts>;

private animation: BezierAnimation | null;

constructor(
ease: Bezier,
duration: number,
getter: (p: P) => number,
setter: (p: P, v: number) => void
setter: (p: P, v: number) => void,
opts?: StdAnimatorOpts
) {
this.ease = ease;
this.duration = duration;
this.getter = getter;
this.setter = setter;
this.opts = Object.assign({}, defaultAnimatorOpts, opts);

this.animation = null;
}
Expand All @@ -113,16 +131,31 @@ export class BezierAnimator<P extends object> implements Animator<P> {
return 'done';
}

this.animation = updateAnim({
now,
current: this.getter(toRender),
target: this.getter(target),
oldAnim: this.animation,
duration: this.duration,
ease: this.ease,
});
if (this.animation !== null && this.animation.to !== this.getter(target)) {
this.animation = {
startedAt: this.opts.onTargetChange === 'extend' ? now : this.animation.startedAt,
from: this.opts.onTargetChange === 'extend' ? this.getter(toRender) : this.animation.from,
to: this.getter(target),
ease: this.ease,
duration: this.duration,
};

if (animIsComplete(this.animation, now)) {
this.animation = null;
}
}

if (this.animation === null) {
this.animation = {
startedAt: now,
from: this.getter(toRender),
to: this.getter(target),
ease: this.ease,
duration: this.duration,
};
}

if (animIsComplete(this.animation, now)) {
this.setter(toRender, this.getter(target));
this.animation = null;
return 'done';
Expand Down
40 changes: 6 additions & 34 deletions src/user/core/features/pickEmotionJourney/PickEmotion.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,49 +57,22 @@
}

.word {
position: absolute;
cursor: pointer;
outline: none;
appearance: none;
-webkit-tap-highlight-color: transparent;
box-shadow: none;
border: none;
position: absolute;
font-family: 'Open Sans', sans-serif;
text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
color: white;
transition: font-size 0.7s ease, padding 0.7s ease, left 0.7s ease, top 0.7s ease,
opacity 0.7s ease, background-color 0.3s ease;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(2px);
opacity: 100%;
}

.horizontalWords.wordsWithPressed .word:not(.pressedWord) {
opacity: 50%;
}

.wordsWithPressed .pressedWord {
opacity: 100%;
background: linear-gradient(95.08deg, #57b8a2 2.49%, #009999 97.19%);
}

.word:hover:not(.pressedWord) {
background: rgba(255, 255, 255, 0.3);
}

.horizontalWord {
font-size: 16px;
border-radius: 24px;
padding: 12px 14px;
line-height: 16px;
backdrop-filter: 'blur(2px)';
font-family: 'Open Sans', sans-serif;
line-height: 100%;
}

.verticalWord {
font-size: 14px;
line-height: 14px;
letter-spacing: 0.25px;
padding: 10px 12px;
border-radius: 24px;
.word:not(.pressed):hover {
background: rgba(255, 255, 255, 0.3) !important;
}

.votes {
Expand All @@ -110,7 +83,6 @@
font-size: 12px;
line-height: 12px;
color: white;
transition: left 0.7s ease, top 0.7s ease, opacity 0.7s ease;
}

.profilePicturesContainer {
Expand Down
Loading

0 comments on commit 5df1cc1

Please sign in to comment.