dlqqq / gif.tsx-demo Goto Github PK
View Code? Open in Web Editor NEWA quick demo of the first version of gif.tsx
A quick demo of the first version of gif.tsx
I finally managed to get it to work with my code but I get the following error after 1 or 2 seconds of running:
It plays Gif well enough.
I have only changed a little bit of code just to add dynamic src
like:
// inspired by gif-player web component -> https://github.com/WillsonSmith/gif-player-component/blob/main/gif-player.js
// converted to react by https://stackoverflow.com/a/68494363/6141587
import React from 'react'
import { useGifController } from '@/hooks/index'
interface IGifPlayer {
alt: string
src: string
}
export const GifPlayer = (gif: IGifPlayer): JSX.Element | null => {
const [gifPath, setGifPath] = React.useState('')
const canvasRef = React.useRef<HTMLCanvasElement>(null)
const gifController = useGifController(gifPath, canvasRef, true)
React.useEffect(() => {
const postType = window.location.pathname.split('/')[1]
setGifPath(`/${postType}/${gif.src}`)
}, [gif.src])
if (gifController.loading) {
return null
}
if (gifController.error) {
return null
}
const { playing, play, pause, restart, renderNextFrame, renderPreviousFrame, width, height } =
gifController
return (
<div>
<canvas {...gifController.canvasProps} ref={canvasRef} />
<div className="flex justify-around gap-3">
<button onClick={renderPreviousFrame}>Previous</button>
{playing ? <button onClick={pause}>Pause</button> : <button onClick={play}>Play</button>}
<button onClick={restart}>Restart</button>
<button onClick={renderNextFrame}>Next</button>
</div>
</div>
)
}
import React from 'react'
import { GifReader } from 'omggif'
export type Frame = {
/**
* A full frame of a GIF represented as an ImageData object. This can be
* rendered onto a canvas context simply by calling
* `ctx.putImageData(frame.imageData, 0, 0)`.
*/
imageData: ImageData
/**
* Delay in milliseconds.
*/
delay: number
}
/**
* Function that accepts a `GifReader` instance and returns an array of
* `ImageData` objects that represent the frames of the gif.
*
* @param gifReader The `GifReader` instance.
* @returns An array of `ImageData` objects representing each frame of the GIF.
* Or `null` if something went wrong.
*/
export function extractFrames(gifReader: GifReader): Frame[] | null {
const frames: Frame[] = []
// the width and height of the complete gif
const { width, height } = gifReader
// This is the primary canvas that the tempCanvas below renders on top of. The
// reason for this is that each frame stored internally inside the GIF is a
// "diff" from the previous frame. To resolve frame 4, we must first resolve
// frames 1, 2, 3, and then render frame 4 on top. This canvas accumulates the
// previous frames.
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
if (!ctx) return null
for (let frameIndex = 0; frameIndex < gifReader.numFrames(); frameIndex++) {
// the width, height, x, and y of the "dirty" pixels that should be redrawn
const {
width: dirtyWidth,
height: dirtyHeight,
x: dirtyX,
y: dirtyY,
disposal,
delay,
} = gifReader.frameInfo(0)
// skip this frame if disposal >= 2; from GIF spec
if (disposal >= 2) continue
// create hidden temporary canvas that exists only to render the "diff"
// between the previous frame and the current frame
const tempCanvas = document.createElement('canvas')
tempCanvas.width = width
tempCanvas.height = height
const tempCtx = tempCanvas.getContext('2d')
if (!tempCtx) return null
// extract GIF frame data to tempCanvas
const newImageData = tempCtx.createImageData(width, height)
gifReader.decodeAndBlitFrameRGBA(frameIndex, newImageData.data)
tempCtx.putImageData(newImageData, 0, 0, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
// draw the tempCanvas on top. ctx.putImageData(tempCtx.getImageData(...))
// is too primitive here, since the pixels would be *replaced* by incoming
// RGBA values instead of layered.
ctx.drawImage(tempCanvas, 0, 0)
frames.push({
delay: delay * 10,
imageData: ctx.getImageData(0, 0, width, height),
})
}
return frames
}
type HTMLCanvasElementProps = React.DetailedHTMLProps<
React.CanvasHTMLAttributes<HTMLCanvasElement>,
HTMLCanvasElement
>
type GifControllerLoading = {
canvasProps: HTMLCanvasElementProps
loading: true
error: false
}
type GifControllerError = {
canvasProps: HTMLCanvasElementProps
loading: false
error: true
errorMessage: string
}
type GifControllerResolved = {
canvasProps: HTMLCanvasElementProps
loading: false
error: false
frameIndex: React.MutableRefObject<number>
playing: boolean
play: () => void
pause: () => void
restart: () => void
renderFrame: (frame: number) => void
renderNextFrame: () => void
renderPreviousFrame: () => void
width: number
height: number
}
type GifController = GifControllerLoading | GifControllerResolved | GifControllerError
export const useGifController = (
url: string,
canvas: React.RefObject<HTMLCanvasElement | null>,
autoplay = false
): GifController => {
type LoadingState = {
loading: true
error: false
}
type ErrorState = {
loading: false
error: true
errorMessage: string
}
type ResolvedState = {
loading: false
error: false
gifReader: GifReader
frames: Frame[]
}
type State = LoadingState | ResolvedState | ErrorState
const ctx = canvas.current?.getContext('2d')
// asynchronous state variables strongly typed as a union such that properties
// are only defined when `loading === true`.
const [state, setState] = React.useState<State>({ loading: true, error: false })
const [shouldUpdate, setShouldUpdate] = React.useState(false)
const [canvasAccessible, setCanvasAccessible] = React.useState(false)
const frameIndex = React.useRef(-1)
// state variable returned by hook
const [playing, setPlaying] = React.useState(false)
// ref that is used internally
const _playing = React.useRef(false)
// Load GIF on initial render and when url changes.
React.useEffect(() => {
async function loadGif() {
const response = await fetch(url)
const buffer = await response.arrayBuffer()
const uInt8Array = new Uint8Array(buffer)
// Type cast is necessary because GifReader expects Buffer, which extends
// Uint8Array. Doesn't *seem* to cause any runtime errors, but I'm sure
// there's some edge case I'm not covering here.
const gifReader = new GifReader(uInt8Array as Buffer)
const frames = extractFrames(gifReader)
if (!frames) {
setState({
loading: false,
error: true,
errorMessage: 'Could not extract frames from GIF.',
})
} else {
setState({ loading: false, error: false, gifReader, frames })
}
// must trigger re-render to ensure access to canvas ref
setShouldUpdate(true)
}
loadGif()
// only run this effect on initial render and when URL changes.
// eslint-disable-next-line
}, [url])
// update if shouldUpdate gets set to true
React.useEffect(() => {
if (shouldUpdate) {
setShouldUpdate(false)
} else if (canvas.current !== null) {
setCanvasAccessible(true)
}
}, [canvas, shouldUpdate])
// if canvasAccessible is set to true, render first frame and then autoplay if
// specified in hook arguments
React.useEffect(() => {
if (canvasAccessible && frameIndex.current === -1) {
renderNextFrame()
autoplay && setPlaying(true)
}
// ignore renderNextFrame as it is referentially unstable
// eslint-disable-next-line
}, [canvasAccessible])
React.useEffect(() => {
if (playing) {
_playing.current = true
_iterateRenderLoop()
} else {
_playing.current = false
}
// ignore _iterateRenderLoop() as it is referentially unstable
// eslint-disable-next-line
}, [playing])
if (state.loading === true || !canvas)
return { canvasProps: { hidden: true }, loading: true, error: false }
if (state.error === true)
return {
canvasProps: { hidden: true },
loading: false,
error: true,
errorMessage: state.errorMessage,
}
const { width, height } = state.gifReader
return {
canvasProps: { width, height },
loading: false,
error: false,
playing,
play,
pause,
restart,
frameIndex,
renderFrame,
renderNextFrame,
renderPreviousFrame,
width,
height,
}
function play() {
if (state.error || state.loading) return
if (playing) return
setPlaying(true)
}
function _iterateRenderLoop() {
if (state.error || state.loading || !_playing.current) return
const delay = state.frames[frameIndex.current].delay
setTimeout(() => {
renderNextFrame()
_iterateRenderLoop()
}, delay)
}
function pause() {
setPlaying(false)
}
function restart() {
frameIndex.current = 0
setPlaying(true)
}
function renderFrame(frameIndex: number) {
if (!ctx || state.loading === true || state.error === true) return
if (frameIndex < 0 || frameIndex >= state.gifReader.numFrames()) return
ctx.putImageData(state.frames[frameIndex].imageData, 0, 0)
}
function renderNextFrame() {
if (!ctx || state.loading === true || state.error === true) return
const nextFrame =
frameIndex.current + 1 >= state.gifReader.numFrames() ? 0 : frameIndex.current + 1
renderFrame(nextFrame)
frameIndex.current = nextFrame
}
function renderPreviousFrame() {
if (!ctx || state.loading === true || state.error === true) return
const prevFrame =
frameIndex.current - 1 < 0 ? state.gifReader.numFrames() - 1 : frameIndex.current - 1
renderFrame(prevFrame)
frameIndex.current = prevFrame
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.