From e051c01bc7470daa6cc31708728dd063b3acf552 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Wed, 20 Jul 2022 23:53:08 +0200 Subject: [PATCH] Make a megafancy animation for postcard switching --- components/generic/toolbar/bar.module.css | 2 +- components/postcard/{base.ts => base.tsx} | 13 ++-- components/postcard/changer.tsx | 24 +++---- components/postcard/provider.tsx | 4 +- components/postcard/renderer.module.css | 38 ++++++++-- components/postcard/renderer.tsx | 51 ++++++++++---- components/postcard/storage.ts | 84 ++++++++++++++++++++--- next.config.js | 13 ---- utils/asleep.ts | 2 +- 9 files changed, 170 insertions(+), 61 deletions(-) rename components/postcard/{base.ts => base.tsx} (72%) diff --git a/components/generic/toolbar/bar.module.css b/components/generic/toolbar/bar.module.css index 9b96618..96ec935 100644 --- a/components/generic/toolbar/bar.module.css +++ b/components/generic/toolbar/bar.module.css @@ -6,7 +6,7 @@ position: fixed; - z-index: 2; + z-index: 3; gap: 2px; } diff --git a/components/postcard/base.ts b/components/postcard/base.tsx similarity index 72% rename from components/postcard/base.ts rename to components/postcard/base.tsx index 35ac24c..175cc59 100644 --- a/components/postcard/base.ts +++ b/components/postcard/base.tsx @@ -1,12 +1,11 @@ -import { ImageProps } from "next/image" -import { useState } from "react"; +import { ComponentPropsWithoutRef } from "react"; import { createDefinedContext } from "../../utils/definedContext"; /** * The string to be used as the `src` of the postcard. */ -export type PostcardSource = ImageProps["src"] +export type PostcardSource = string /** @@ -29,10 +28,12 @@ export enum PostcardVisibility { * Contents of the {@link PostcardContext}. */ export type PostcardContextContents = { - src: PostcardSource, - setSrc: React.Dispatch>, visibility: PostcardVisibility, - setVisibility: React.Dispatch>, + previousSrc: PostcardSource, + currentSrc: PostcardSource, + changePostcard: (src: PostcardSource) => void, + resetPostcard: () => void, + changeVisibility: (visibility: PostcardVisibility) => void, } diff --git a/components/postcard/changer.tsx b/components/postcard/changer.tsx index d3d69ef..97e5960 100644 --- a/components/postcard/changer.tsx +++ b/components/postcard/changer.tsx @@ -1,3 +1,4 @@ +import { StaticImageData } from "next/image" import { useEffect } from "react" import { useDefinedContext } from "../../utils/definedContext" import { PostcardContext, PostcardSource } from "./base" @@ -6,28 +7,27 @@ import { PostcardContext, PostcardSource } from "./base" /** * Use the passed src as {@link PostcardSource} for the wrapping {@link PostcardContext}. */ -export function usePostcardImage(src: PostcardSource) { - const { setSrc } = useDefinedContext(PostcardContext) +export function usePostcardImage(src: PostcardSource | StaticImageData) { + const { changePostcard } = useDefinedContext(PostcardContext) useEffect( () => { - setSrc(src) + if (typeof src === "string") { + changePostcard(src) + } + else { + changePostcard(src.src) + } }, - [src, setSrc] + [src, changePostcard] ) } -export type PostcardProps = { - src: PostcardSource -} - - /** * The same as {@link usePostcardImage}, but as a component rendering `null`. */ -export function Postcard(props: PostcardProps) { - usePostcardImage(props.src) - +export function Postcard({ src }: { src: PostcardSource | StaticImageData }): null { + usePostcardImage(src) return null } diff --git a/components/postcard/provider.tsx b/components/postcard/provider.tsx index 483bd9b..6d6fe23 100644 --- a/components/postcard/provider.tsx +++ b/components/postcard/provider.tsx @@ -1,6 +1,6 @@ import React from "react" import { PostcardContext, PostcardSource } from "./base" -import { useStatePostcard } from "./storage" +import { usePostcardStorage } from "./storage" export type PostcardContextProviderProps = { @@ -11,7 +11,7 @@ export type PostcardContextProviderProps = { export const PostcardContextProvider = ({ defaultPostcard, children }: PostcardContextProviderProps) => { return ( - + {children} ) diff --git a/components/postcard/renderer.module.css b/components/postcard/renderer.module.css index 50f1969..db9af01 100644 --- a/components/postcard/renderer.module.css +++ b/components/postcard/renderer.module.css @@ -11,12 +11,9 @@ user-select: none; pointer-events: none; - - z-index: -1; } .postcardBackground { - z-index: -1; filter: blur(7px) contrast(50%) brightness(50%); } @@ -27,6 +24,39 @@ } .postcardForeground { - z-index: 1; filter: none; +} + +.postcardPrevious.postcardBackground { + z-index: -2; +} + +.postcardCurrent.postcardBackground { + z-index: -1; +} + +.postcardPrevious.postcardForeground { + z-index: 1; +} + +.postcardCurrent.postcardForeground { + z-index: 2; +} + +.postcardAppearing { + opacity: 0; + animation: appear 1s linear infinite; +} + +.postcardAppeared { + opacity: 1; +} + +@keyframes appear { + from { + opacity: 0; + } + to { + opacity: 1; + } } \ No newline at end of file diff --git a/components/postcard/renderer.tsx b/components/postcard/renderer.tsx index 1d0d61a..adce386 100644 --- a/components/postcard/renderer.tsx +++ b/components/postcard/renderer.tsx @@ -1,30 +1,53 @@ import { default as classNames } from "classnames" import style from "./renderer.module.css" -import Image, { ImageProps } from "next/future/image" import { useDefinedContext } from "../../utils/definedContext" import { PostcardContext, PostcardVisibility } from "./base" +import { LegacyRef, useEffect, useRef, useState } from "react" +import { asleep } from "../../utils/asleep" -export function PostcardRenderer(props: Partial) { - const { src, visibility } = useDefinedContext(PostcardContext) +export function PostcardRenderer() { + const { previousSrc, currentSrc, visibility } = useDefinedContext(PostcardContext) + const currentRef: LegacyRef = useRef(null) - // Hehe, dirty hack that might actually work - const width = typeof window === "undefined" ? undefined : window.innerWidth - const height = typeof window === "undefined" ? undefined : window.innerHeight + useEffect( + () => { + if (currentRef.current) { + currentRef.current.animate( + [ + { opacity: 0 }, + { opacity: 1 }, + ], + { + duration: 1000 + } + ) + } + }, + [currentRef, currentSrc] + ) - return ( - + - ) + + } \ No newline at end of file diff --git a/components/postcard/storage.ts b/components/postcard/storage.ts index c1e792e..71bc198 100644 --- a/components/postcard/storage.ts +++ b/components/postcard/storage.ts @@ -1,17 +1,85 @@ -import { useState } from "react"; -import { PostcardSource, PostcardVisibility } from "./base"; +import { Reducer, useCallback, useReducer } from "react" +import { PostcardContextContents, PostcardSource, PostcardVisibility } from "./base" + + +/** + * Action of {@link usePostcardStorage} changing the current postcard to a new one. + */ +type UsePostcardStorageActionChange = { type: "change", src: PostcardSource } + +/** + * Action of {@link usePostcardStorage} changing the visibility of the current postcard. + */ +type UsePostcardStorageActionDisplay = { type: "display", visibility: PostcardVisibility } + +/** + * All possible actions of the reducer of {@link usePostcardStorage}. + */ +type UsePostcardStorageAction = UsePostcardStorageActionChange | UsePostcardStorageActionDisplay + +/** + * The state of the reducer of {@link usePostcardStorage}. + */ +type UsePostcardStorageState = { + visibility: PostcardVisibility, + currentSrc: PostcardSource, + previousSrc: PostcardSource, +} + +function reducerUsePostcardStorage(prev: UsePostcardStorageState, action: UsePostcardStorageAction): UsePostcardStorageState { + switch (action.type) { + case "change": + if (action.src !== prev.currentSrc) { + return { ...prev, previousSrc: prev.currentSrc, currentSrc: action.src } + } + else { + return prev + } + case "display": + return { ...prev, visibility: action.visibility } + } +} /** * Hook holding as state the {@link PostcardContextContents}. */ -export function useStatePostcard(defaultPostcard: PostcardSource) { - const [src, setSrc] = useState(defaultPostcard); - const [visibility, setVisibility] = useState(PostcardVisibility.BACKGROUND); +export function usePostcardStorage(defaultPostcard: PostcardSource): PostcardContextContents { + const [{ previousSrc, currentSrc, visibility }, dispatch] = useReducer>( + reducerUsePostcardStorage, + { + visibility: PostcardVisibility.BACKGROUND, + previousSrc: defaultPostcard, + currentSrc: defaultPostcard + } + ) + + const changePostcard = useCallback( + (src: PostcardSource) => { + dispatch({ type: "change", src }) + }, + [dispatch] + ) + + const resetPostcard = useCallback( + () => { + changePostcard(defaultPostcard) + }, + [changePostcard, defaultPostcard] + ) + + const changeVisibility = useCallback( + (visibility: PostcardVisibility) => { + dispatch({ type: "display", visibility }) + }, + [dispatch] + ) return { - src, - setSrc, + previousSrc, + currentSrc, + changePostcard, + resetPostcard, + changeVisibility, visibility, - setVisibility, }; } diff --git a/next.config.js b/next.config.js index 053fc44..172a8bc 100644 --- a/next.config.js +++ b/next.config.js @@ -60,19 +60,6 @@ function webpack(config) { * @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - images: { - allowFutureImage: true, - remotePatterns: [ - { - protocol: "https", - hostname: "images.unsplash.com", - port: "", - pathname: "/photo-*" - } - ] - } - }, reactStrictMode: true, webpack, i18n, diff --git a/utils/asleep.ts b/utils/asleep.ts index 8b159de..a2cbbb3 100644 --- a/utils/asleep.ts +++ b/utils/asleep.ts @@ -1,3 +1,3 @@ -export async function asleep(ms: number) { +export async function asleep(ms: number): Promise { return await new Promise(r => setTimeout(r, ms)); } \ No newline at end of file