admin管理员组

文章数量:1355991

I've been attempting to create a pixel animation as a CSS background. The animation is accomplished through the React component below, and works except for the fact that I cannot seem to get the background image to rescale reponsively to the viewport:

"use client";

import React, { useState, useEffect, useRef } from "react";
import Loader from "@/components/Loader";
import styles from "./Home.module.css";

const spritesheet = "/pixelart/spacesprites.png";
const spriteDataUrl = "/pixelart/spacesprites.json";

export default function AnimatedBackground({ children }) {
  const [frame, setFrame] = useState(0);
  const [isLoaded, setIsLoaded] = useState(false);
  const [spriteData, setSpriteData] = useState(null);
  const animationSpeed = 100; // Adjust for desired animation speed (milliseconds)
  const animationInterval = useRef(null);

  useEffect(() => {
    const preloadAssets = async () => {
      try {
        // Load image
        const img = new Image();
        img.src = spritesheet;
        await img.decode(); // Ensure image is fully loaded

        // Load JSON
        const res = await fetch(spriteDataUrl);
        const data = await res.json();
        setSpriteData(data);

        // Mark as loaded
        setIsLoaded(true);
      } catch (error) {
        console.error("Failed to load assets:", error);
      }
    };

    preloadAssets();
  }, []);

  useEffect(() => {
    if (!isLoaded || !spriteData) return;

    const frames = spriteData.frames;
    const frameCount = Object.keys(frames).length;
    let currentFrame = 0;

    animationInterval.current = setInterval(() => {
      setFrame(currentFrame);
      currentFrame = (currentFrame + 1) % frameCount;
    }, animationSpeed);

    return () => clearInterval(animationInterval.current);
  }, [isLoaded, spriteData]);

  if (!isLoaded || !spriteData) {
    return (
      <Loader />
    );
  }

  const frameData = spriteData.frames[Object.keys(spriteData.frames)[frame]].frame;

  const backgroundStyle = {
    backgroundImage: `url(${spritesheet})`,
    width: frameData.w,
    height: frameData.h,
    backgroundPosition: `-${frameData.x}px -${frameData.y}px`,
  };

  return (
    <div 
      className={`${styles['twinkle-background']}`}
      style={backgroundStyle}
    >
      {children}
    </div>
  );
};

My corresponding CSS is:

.twinkle-background {
  display: flex;
  justify-content: center;
  image-rendering: crisp-edges;
}

To summarize the problem, each frame in my sprite sheet is 1920 x 1440 pixels. This is what frameData.w and frameData.h in the component correspond to. Because the width and height in the inline style is defined as such, however, I'm stumped as to how to ensure that the background image conforms to the dimensions of the viewport.

This component is wrapped in a div with corresponding CSS:

.twinkle {
  width: 100vw;
  height: auto;
  display: flex;
  justify-content: center;
}

and if it helps, the corresponding JSON:

{ "frames": {
   "space 0.aseprite": {
    "frame": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 1.aseprite": {
    "frame": { "x": 1920, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 2.aseprite": {
    "frame": { "x": 3840, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 3.aseprite": {
    "frame": { "x": 5760, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 4.aseprite": {
    "frame": { "x": 7680, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 5.aseprite": {
    "frame": { "x": 9600, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 6.aseprite": {
    "frame": { "x": 11520, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 7.aseprite": {
    "frame": { "x": 13440, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 8.aseprite": {
    "frame": { "x": 15360, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 9.aseprite": {
    "frame": { "x": 17280, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 10.aseprite": {
    "frame": { "x": 19200, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 11.aseprite": {
    "frame": { "x": 21120, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 12.aseprite": {
    "frame": { "x": 23040, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 13.aseprite": {
    "frame": { "x": 24960, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   },
   "space 14.aseprite": {
    "frame": { "x": 26880, "y": 0, "w": 1920, "h": 1440 },
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": { "x": 0, "y": 0, "w": 1920, "h": 1440 },
    "sourceSize": { "w": 1920, "h": 1440 },
    "duration": 100
   }
 },
 "meta": {
  "app": "/",
  "version": "1.3.13-x64",
  "image": "spacesprites.png",
  "format": "RGBA8888",
  "size": { "w": 28800, "h": 1440 },
  "scale": "1",
  "frameTags": [
  ],
  "layers": [
   { "name": "Background", "opacity": 255, "blendMode": "normal" },
   { "name": "Stars", "opacity": 255, "blendMode": "normal" },
   { "name": "Stars2", "opacity": 255, "blendMode": "normal" },
   { "name": "Stars3", "opacity": 128, "blendMode": "normal" },
   { "name": "Stars4", "opacity": 128, "blendMode": "normal" },
   { "name": "Stars5", "opacity": 128, "blendMode": "normal" },
   { "name": "MilkyWay01", "opacity": 255, "blendMode": "normal" },
   { "name": "MilkyWay02", "opacity": 255, "blendMode": "normal" },
   { "name": "MilkyWay03", "opacity": 255, "blendMode": "normal" },
   { "name": "MilkyWay04", "opacity": 255, "blendMode": "normal" },
   { "name": "MilkyWay05", "opacity": 255, "blendMode": "normal" },
   { "name": "LargeStars01", "opacity": 255, "blendMode": "normal" },
   { "name": "MediumStars01", "opacity": 255, "blendMode": "normal" },
   { "name": "RedNebula01", "opacity": 255, "blendMode": "normal" },
   { "name": "RedNebula02", "opacity": 255, "blendMode": "normal" },
   { "name": "RedNebula03", "opacity": 255, "blendMode": "normal" },
   { "name": "RedNebula05", "opacity": 255, "blendMode": "normal" },
   { "name": "Planet01", "opacity": 255, "blendMode": "normal" },
   { "name": "Foreground", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar010", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar016", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar011", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar012", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar013", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar014", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar015", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar020", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar021", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar022", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar023", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar024", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar030", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar031", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar032", "opacity": 255, "blendMode": "normal" },
   { "name": "AnimateStar033", "opacity": 255, "blendMode": "normal" }
  ],
  "slices": [
  ]
 }
}

Any advice on how to revise my CSS to achieve the viewport responsiveness would be much appreciated.

本文标签: reactjsResponsive CSS sprite animation backgroundStack Overflow