1
Fork 0
mirror of https://github.com/Steffo99/festa.git synced 2024-10-16 06:57:26 +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;
z-index: 2;
z-index: 3;
gap: 2px;
}

View file

@ -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<React.SetStateAction<PostcardSource>>,
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 { 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)
},
[src, setSrc]
)
if (typeof src === "string") {
changePostcard(src)
}
export type PostcardProps = {
src: PostcardSource
else {
changePostcard(src.src)
}
},
[src, changePostcard]
)
}
/**
* 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
}

View file

@ -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 (
<PostcardContext.Provider value={useStatePostcard(defaultPostcard)}>
<PostcardContext.Provider value={usePostcardStorage(defaultPostcard)}>
{children}
</PostcardContext.Provider>
)

View file

@ -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;
}
}

View file

@ -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<ImageProps>) {
const { src, visibility } = useDefinedContext(PostcardContext)
export function PostcardRenderer() {
const { previousSrc, currentSrc, visibility } = useDefinedContext(PostcardContext)
const currentRef: LegacyRef<HTMLImageElement> = 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 (
<Image
src={src}
return <>
<img
src={previousSrc}
alt=""
priority={true}
{...props}
width={width}
height={height}
className={classNames(
style.postcard,
style.postcardPrevious,
visibility === PostcardVisibility.BACKGROUND ? style.postcardBackground : 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 { 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<PostcardSource>(defaultPostcard);
const [visibility, setVisibility] = useState<PostcardVisibility>(PostcardVisibility.BACKGROUND);
export function usePostcardStorage(defaultPostcard: PostcardSource): PostcardContextContents {
const [{ previousSrc, currentSrc, visibility }, dispatch] = useReducer<Reducer<UsePostcardStorageState, UsePostcardStorageAction>>(
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,
};
}

View file

@ -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,

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));
}