1
Fork 0
mirror of https://github.com/Steffo99/festa.git synced 2024-12-22 06:34:22 +00:00

Make a megafancy animation for postcard switching

This commit is contained in:
Steffo 2022-07-20 23:53:08 +02:00
parent c8acc48f2e
commit e051c01bc7
Signed by: steffo
GPG key ID: 6965406171929D01
9 changed files with 170 additions and 61 deletions

View file

@ -6,7 +6,7 @@
position: fixed; position: fixed;
z-index: 2; z-index: 3;
gap: 2px; gap: 2px;
} }

View file

@ -1,12 +1,11 @@
import { ImageProps } from "next/image" import { ComponentPropsWithoutRef } from "react";
import { useState } from "react";
import { createDefinedContext } from "../../utils/definedContext"; import { createDefinedContext } from "../../utils/definedContext";
/** /**
* The string to be used as the `src` of the postcard. * 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}. * Contents of the {@link PostcardContext}.
*/ */
export type PostcardContextContents = { export type PostcardContextContents = {
src: PostcardSource,
setSrc: React.Dispatch<React.SetStateAction<PostcardSource>>,
visibility: PostcardVisibility, visibility: PostcardVisibility,
setVisibility: React.Dispatch<React.SetStateAction<PostcardVisibility>>, previousSrc: PostcardSource,
currentSrc: PostcardSource,
changePostcard: (src: PostcardSource) => void,
resetPostcard: () => void,
changeVisibility: (visibility: PostcardVisibility) => void,
} }

View file

@ -1,3 +1,4 @@
import { StaticImageData } from "next/image"
import { useEffect } from "react" import { useEffect } from "react"
import { useDefinedContext } from "../../utils/definedContext" import { useDefinedContext } from "../../utils/definedContext"
import { PostcardContext, PostcardSource } from "./base" 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}. * Use the passed src as {@link PostcardSource} for the wrapping {@link PostcardContext}.
*/ */
export function usePostcardImage(src: PostcardSource) { export function usePostcardImage(src: PostcardSource | StaticImageData) {
const { setSrc } = useDefinedContext(PostcardContext) const { changePostcard } = useDefinedContext(PostcardContext)
useEffect( 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`. * The same as {@link usePostcardImage}, but as a component rendering `null`.
*/ */
export function Postcard(props: PostcardProps) { export function Postcard({ src }: { src: PostcardSource | StaticImageData }): null {
usePostcardImage(props.src) usePostcardImage(src)
return null return null
} }

View file

@ -1,6 +1,6 @@
import React from "react" import React from "react"
import { PostcardContext, PostcardSource } from "./base" import { PostcardContext, PostcardSource } from "./base"
import { useStatePostcard } from "./storage" import { usePostcardStorage } from "./storage"
export type PostcardContextProviderProps = { export type PostcardContextProviderProps = {
@ -11,7 +11,7 @@ export type PostcardContextProviderProps = {
export const PostcardContextProvider = ({ defaultPostcard, children }: PostcardContextProviderProps) => { export const PostcardContextProvider = ({ defaultPostcard, children }: PostcardContextProviderProps) => {
return ( return (
<PostcardContext.Provider value={useStatePostcard(defaultPostcard)}> <PostcardContext.Provider value={usePostcardStorage(defaultPostcard)}>
{children} {children}
</PostcardContext.Provider> </PostcardContext.Provider>
) )

View file

@ -11,12 +11,9 @@
user-select: none; user-select: none;
pointer-events: none; pointer-events: none;
z-index: -1;
} }
.postcardBackground { .postcardBackground {
z-index: -1;
filter: blur(7px) contrast(50%) brightness(50%); filter: blur(7px) contrast(50%) brightness(50%);
} }
@ -27,6 +24,39 @@
} }
.postcardForeground { .postcardForeground {
z-index: 1;
filter: none; 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;
}
} }

View file

@ -1,30 +1,53 @@
import { default as classNames } from "classnames" import { default as classNames } from "classnames"
import style from "./renderer.module.css" import style from "./renderer.module.css"
import Image, { ImageProps } from "next/future/image"
import { useDefinedContext } from "../../utils/definedContext" import { useDefinedContext } from "../../utils/definedContext"
import { PostcardContext, PostcardVisibility } from "./base" import { PostcardContext, PostcardVisibility } from "./base"
import { LegacyRef, useEffect, useRef, useState } from "react"
import { asleep } from "../../utils/asleep"
export function PostcardRenderer(props: Partial<ImageProps>) { export function PostcardRenderer() {
const { src, visibility } = useDefinedContext(PostcardContext) const { previousSrc, currentSrc, visibility } = useDefinedContext(PostcardContext)
const currentRef: LegacyRef<HTMLImageElement> = useRef(null)
// Hehe, dirty hack that might actually work useEffect(
const width = typeof window === "undefined" ? undefined : window.innerWidth () => {
const height = typeof window === "undefined" ? undefined : window.innerHeight if (currentRef.current) {
currentRef.current.animate(
[
{ opacity: 0 },
{ opacity: 1 },
],
{
duration: 1000
}
)
}
},
[currentRef, currentSrc]
)
return ( return <>
<Image <img
src={src} src={previousSrc}
alt="" alt=""
priority={true}
{...props}
width={width}
height={height}
className={classNames( className={classNames(
style.postcard, style.postcard,
style.postcardPrevious,
visibility === PostcardVisibility.BACKGROUND ? style.postcardBackground : null, visibility === PostcardVisibility.BACKGROUND ? style.postcardBackground : null,
visibility === PostcardVisibility.FOREGROUND ? style.postcardForeground : null, visibility === PostcardVisibility.FOREGROUND ? style.postcardForeground : null,
)} )}
/> />
) <img
src={currentSrc}
alt=""
ref={currentRef}
className={classNames(
style.postcard,
style.postcardCurrent,
visibility === PostcardVisibility.BACKGROUND ? style.postcardBackground : null,
visibility === PostcardVisibility.FOREGROUND ? style.postcardForeground : null,
)}
/>
</>
} }

View file

@ -1,17 +1,85 @@
import { useState } from "react"; import { Reducer, useCallback, useReducer } from "react"
import { PostcardSource, PostcardVisibility } from "./base"; 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}. * Hook holding as state the {@link PostcardContextContents}.
*/ */
export function useStatePostcard(defaultPostcard: PostcardSource) { export function usePostcardStorage(defaultPostcard: PostcardSource): PostcardContextContents {
const [src, setSrc] = useState<PostcardSource>(defaultPostcard); const [{ previousSrc, currentSrc, visibility }, dispatch] = useReducer<Reducer<UsePostcardStorageState, UsePostcardStorageAction>>(
const [visibility, setVisibility] = useState<PostcardVisibility>(PostcardVisibility.BACKGROUND); 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 { return {
src, previousSrc,
setSrc, currentSrc,
changePostcard,
resetPostcard,
changeVisibility,
visibility, visibility,
setVisibility,
}; };
} }

View file

@ -60,19 +60,6 @@ function webpack(config) {
* @type {import('next').NextConfig} * @type {import('next').NextConfig}
*/ */
const nextConfig = { const nextConfig = {
experimental: {
images: {
allowFutureImage: true,
remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com",
port: "",
pathname: "/photo-*"
}
]
}
},
reactStrictMode: true, reactStrictMode: true,
webpack, webpack,
i18n, i18n,

View file

@ -1,3 +1,3 @@
export async function asleep(ms: number) { export async function asleep(ms: number): Promise<void> {
return await new Promise(r => setTimeout(r, ms)); return await new Promise(r => setTimeout(r, ms));
} }