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:
parent
c8acc48f2e
commit
e051c01bc7
9 changed files with 170 additions and 61 deletions
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
||||||
z-index: 2;
|
z-index: 3;
|
||||||
|
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
}
|
}
|
|
@ -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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
Loading…
Reference in a new issue