Skip to content

Commit

Permalink
Implement JS animation API
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewp committed Jul 11, 2023
1 parent b998384 commit 7c2f10b
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 28 deletions.
4 changes: 3 additions & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export interface TransitionDirectionalAnimations {
backwards: TransitionAnimationPair;
}

export type TransitionAnimationValue = 'morph' | 'slide' | 'fade' | TransitionDirectionalAnimations;

// Allow users to extend this for astro-jsx.d.ts
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface AstroClientDirectives {}
Expand All @@ -88,7 +90,7 @@ export interface AstroBuiltinAttributes {
'set:html'?: any;
'set:text'?: any;
'is:raw'?: boolean;
'transition:animate'?: 'morph' | 'slide' | 'fade' | TransitionDirectionalAnimations;
'transition:animate'?: TransitionAnimationValue;
'transition:name'?: string;
}

Expand Down
133 changes: 107 additions & 26 deletions packages/astro/src/runtime/server/transition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { SSRResult } from '../../@types/astro';
import type {
SSRResult,
TransitionAnimation,
TransitionDirectionalAnimations,
TransitionAnimationValue,
} from '../../@types/astro';
import { markHTMLString } from './escape.js';
import { slide, fade } from '../../transitions/index.js';

const animations = {
const animationsOld = {
'slide': {
old: '--astro-animate-old-slideout',
new: '--astro-animate-new-slidein',
Expand All @@ -28,12 +34,23 @@ function createTransitionScope(result: SSRResult, hash: string) {
const num = incrementTransitionNumber(result);
return `astro-${hash}-${num}`;
}
export function renderTransition(result: SSRResult, hash: string, animationName: string | undefined, transitionName: string) {
// Default animation is morph
if(!animationName) {
animationName = "morph";
export function renderTransition(result: SSRResult, hash: string, animationName: TransitionAnimationValue | undefined, transitionName: string) {
let animations: TransitionDirectionalAnimations | null = null;
switch(animationName) {
case 'fade': {
animations = fade();
break;
}
case 'slide': {
animations = slide();
break;
}
default: {
if(typeof animationName === 'object') {
animations = animationName;
}
}
}
const animation = animations[animationName as keyof typeof animations];

const scope = createTransitionScope(result, hash);

Expand All @@ -43,29 +60,93 @@ export function renderTransition(result: SSRResult, hash: string, animationName:
}

const styles = markHTMLString(`<style>[data-astro-transition-scope="${scope}"] {
view-transition-name: ${transitionName};
}
${animationName === 'morph' ? '' : `
::view-transition-old(${transitionName}) {
animation: var(${animation.old});
}
::view-transition-new(${transitionName}) {
animation: var(${animation.new});
}
${('backOld' in animation) && ('backNew' in animation) ? `
.astro-back-transition::view-transition-old(${transitionName}) {
animation-name: var(${animation.backOld});
}
.astro-back-transition::view-transition-new(${transitionName}) {
animation-name: var(${animation.backNew});
}
`.trim() : ''}
view-transition-name: ${transitionName};
}
${!animations ? '' : `
::view-transition-old(${transitionName}) {
${stringifyAnimation(animations.forwards.old)}
}
::view-transition-new(${transitionName}) {
${stringifyAnimation(animations.forwards.new)}
}
.astro-back-transition::view-transition-old(${transitionName}) {
${stringifyAnimation(animations.backwards.old)}
}
.astro-back-transition::view-transition-new(${transitionName}) {
${stringifyAnimation(animations.backwards.new)}
}
`.trim()}
</style>`)

result.extraHead.push(styles);

return scope;
}

type AnimationBuilder = {
toString(): string;
[key: string]: string[] | ((k: string) => string);
}

function addAnimationProperty(builder: AnimationBuilder, prop: string, value: string | number) {
let arr = builder[prop];
if(Array.isArray(arr)) {
arr.push(value.toString());
} else {
builder[prop] = [value.toString()];
}
}

function animationBuilder(): AnimationBuilder {
return {
toString() {
let out = '';
for(let k in this) {
let value = this[k];
if(Array.isArray(value)) {
out += `\n\t${k}: ${value.join(', ')};`
}
}
return out;
}
};
}

function stringifyAnimation(anim: TransitionAnimation | TransitionAnimation[]): string {
if(Array.isArray(anim)) {
return stringifyAnimations(anim);
} else {
return stringifyAnimations([anim]);
}
}

function stringifyAnimations(anims: TransitionAnimation[]): string {
const builder = animationBuilder();

for(const anim of anims) {
/*300ms cubic-bezier(0.4, 0, 0.2, 1) both astroSlideFromRight;*/
if(anim.duration) {
addAnimationProperty(builder, 'animation-duration', toTimeValue(anim.duration));
}
if(anim.easing) {
addAnimationProperty(builder, 'animation-timing-function', anim.easing);
}
if(anim.direction) {
addAnimationProperty(builder, 'animation-direction', anim.direction);
}
if(anim.delay) {
addAnimationProperty(builder, 'animation-delay', anim.delay);
}
if(anim.fillMode) {
addAnimationProperty(builder, 'animation-fill-mode', anim.fillMode);
}
addAnimationProperty(builder, 'animation-name', anim.name);
}

return builder.toString();
}

function toTimeValue(num: number | string) {
return typeof num === 'number' ? num + 'ms' : num;
}
2 changes: 1 addition & 1 deletion packages/astro/src/transitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function fade({
duration
}: {
duration?: string | number;
}): TransitionDirectionalAnimations {
} = {}): TransitionDirectionalAnimations {
const anim = {
old: {
name: 'astroFadeInOut',
Expand Down

0 comments on commit 7c2f10b

Please sign in to comment.