最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - Responsive CSS sprite animation background - Stack Overflow

programmeradmin3浏览0评论

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.

发布评论

评论列表(0)

  1. 暂无评论