mirror of
https://github.com/Steffo99/festa.git
synced 2024-12-21 22:24:22 +00:00
Make a megafancy animation for postcard switching
This commit is contained in:
parent
c8acc48f2e
commit
e051c01bc7
9 changed files with 170 additions and 61 deletions
|
@ -6,7 +6,7 @@
|
|||
|
||||
position: fixed;
|
||||
|
||||
z-index: 2;
|
||||
z-index: 3;
|
||||
|
||||
gap: 2px;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
Loading…
Reference in a new issue