Skip to content

Pixel Art Animation Maker — Frames, Onion Skin, Tags

Draw an animation frame by frame in the browser. Frames, tags and durations live on the Studio timeline you also use for slicing and packing, and the Pixel workspace paints each frame with the neighbouring ones shown as onion skin, so a walk cycle can be drawn, played and exported in one place.

A pixel-art animation is a short loop of frames, each shown for its own number of milliseconds. A workable walk cycle is 6 or 8 frames at about 100 ms (0.6–0.8 s for two steps); an idle is 2–4 frames held longer, 150–300 ms each, so the character breathes instead of buzzing. In the Pixel workspace, Alt+N copies the current frame to redraw the next pose, F3 shows onion skin, each frame gets its own duration (100 ms by default) and Enter plays the loop; save .aseprite or export GIF, APNG or engine files per tag.

Drop a sprite, frames, a GIF or an .aseprite file to paint on — or an upscaled picture to clean up
Runs in your browser. Files are not uploaded.
Studio Pixel workspace: six CC0 ninja run frames on the timeline, one frame being edited with onion skin showing the frames before and after
The real Studio with a CC0 asset (OpenGameArt).
Exports to
  • .asepriteVerified· Aseprite 1.3.18
  • Engine bundlesVerified· Godot 4.7.2 · Unity 6000.5
  • Palette filesMeasured· read back
  • Frame PNG · .nerulioPlain files

Frame count, timing and why per-frame durations matter

A walk cycle is two steps. Each step passes through the same key poses: contact (both feet on the ground, legs widest), down (weight lands, body lowest), passing (one leg passes the other) and up (body highest). Four poses per step gives 8 frames; dropping the down pose gives 6. Draw the contact poses first, check them with onion skin, then add the in-betweens.

Timing is milliseconds per frame, not a global frame rate. Eight frames at 100 ms make an 800 ms cycle, so each step takes 400 ms. An idle loop reads better when the extremes are held: 300 ms on the inhale and exhale poses, 150 ms on the in-betweens. Because every frame in Nerulio keeps its own duration, a hold does not need a duplicated frame.

Where the animation goes changes how timing is stored. GIF counts delays in hundredths of a second, so 125 ms becomes 13 hundredths (130 ms) and the exporter reports it; APNG stores the exact milliseconds. Engines that play one speed per animation get an FPS of 1000 ÷ the average duration plus per-frame multipliers from Nerulio's engine exports.

Key pose
A drawing that defines the motion: contact, down, passing and up in a walk.
In-between
A frame drawn between two key poses to smooth the motion.
Onion skin
The previous and next frames shown faintly under the one being drawn.
Hold
A frame shown longer than its neighbours to let a pose read.
Tag
A named frame range (walk, idle) with a direction and a repeat count.

Example: timing a walk and an idle

Durations typed on the timeline, and what each export does with them:

walk   8 frames × 100 ms          = 800 ms per cycle → 400 ms per step
walk   6 frames × 100 ms          = 600 ms per cycle → 300 ms per step (brisker)
idle   4 frames 300,150,300,150   = 900 ms per breath
engine FPS for idle               = 1000 / average(300,150,300,150) = 1000 / 225 = 4.44
       multipliers 300/225 = 1.33, 150/225 = 0.67
GIF    150 ms → 15/100 s (exact)   125 ms → 13/100 s = 130 ms (rounded)
APNG   125 ms → 125/1000 s (exact)
stride foot moves 16 px back over one 400 ms step → move the character 40 px/s

The last line is the check that stops a walk from sliding: the planted foot should travel backwards at the speed the character travels forwards in the game. If the game moves the character faster or slower, change the durations or the stride, not only the movement speed.

What it does

  • New frame as a copy of the current one (Alt+N) or empty (Alt+Shift+N), delete frames with Alt+C, step with , and . and play or stop with Enter. Tags carry a direction (forward, reverse, ping-pong) and a repeat count.
  • Onion skin (F3) shows the previous and next frames under the one you paint, optionally tinted red and blue, with how many of each and their opacity set in the timeline. The preview window (F7) keeps playing the tag at a whole-number zoom while you paint.
  • Each frame keeps its own duration in milliseconds, so a held pose and a fast swing can sit in one animation. Playback was measured keeping 100 ms frames on time (20 frames in 2 s) on a 512×512, 100-frame sprite.
  • Painting a frame of an imported sheet gives that frame its own pixels; the other frames and the source sheet stay untouched, and the change is one undo step.
  • Frames come from anywhere the Sprite workspace reads: a sheet it cuts from its pixels, numbered PNG files, a GIF or APNG with its delays, or an .aseprite file with layers, tags and durations.

How to do it

  1. Drop a GIF, numbered frames or an .aseprite file on this page; they arrive as frames and the Pixel workspace opens. A single sheet opens in the Sprite workspace first so its cut can be checked and applied.
  2. Select a frame on the timeline, turn on onion skin with F3 and redraw the pose with the neighbouring frames visible.
  3. Add in-betweens with Alt+N, type the duration of each frame, and drag across the tag row of the timeline to name the animation.
  4. Press Enter or open the preview window with F7 to watch the loop, then save .aseprite or export GIF or APNG per animation in Pack & Export.

Check the loop before exporting

  • Press Enter and watch at least five loops: a jump or a pause at the seam usually means the last frame repeats the first.
  • Turn on onion skin (F3) on the passing frame: the contact frames on either side should sit symmetrically around it.
  • Open the preview window (F7) at 1× or 2×: timing that feels right when zoomed in often feels slow at game size.
  • After exporting a GIF, read the exporter's notes: it reports every delay rounded to hundredths of a second.

Timeline controls while you animate

The same timeline as the Sprite workspace, driven from the Pixel workspace.

ActionKeyWhat happens
New frame (copy)Alt+NDuplicates the current frame after it, ready to be redrawn
New empty frameAlt+Shift+NAdds a blank frame for a fresh pose
Delete framesAlt+CRemoves the selected frames in one undo step
Onion skinF3Previous and next frames shown under the current one; counts, opacity and tint adjustable
Play / preview windowEnter / F7Plays with per-frame durations; the preview stays sharp at whole-number zoom

Troubleshooting

SymptomLikely causeHow to checkFix
The loop hitches once per cycleThe last frame is a copy of the first, so that pose shows twice in a rowStep with , and . across the seam: two identical frames in a rowDelete the duplicate last frame (Alt+C)
The character slides or moonwalks in the gameStride per cycle does not match the in-game movement speedMeasure how many pixels the planted foot moves per step and divide by the step timeMatch the movement speed to the stride (16 px per 400 ms = 40 px/s) or retime the frames
The GIF plays faster or slower than the previewGIF stores delays in 1/100 s; odd millisecond values are rounded, and the exporter uses at least 20 msThe export notes list the rounded framesUse multiples of 10 ms, or export APNG, which keeps exact milliseconds
Timing is right in Nerulio but not in the engineThe engine was given one FPS for the tag and ignored the per-frame durationsCompare a held frame's length in the engine with the timelineUse the engine bundle from Pack & Export, which writes per-frame multipliers; see frame duration in Godot
Onion skin shows nothingIt is switched off, or its opacity is too low, or you are on the first frame of the rangeTimeline: the onion skin button and its settingsPress F3 and raise the opacity; step to a frame with neighbours

Other ways to do it

OptionWhen it is the better choice
AsepriteYou want linked cels to reuse one drawing across held frames, or tags and onion skin in a desktop app; its documentation covers onion skinning and frame durations.
Assembling frames drawn elsewhereThe poses already exist as PNGs: build the animation in the sprite animator and set durations there.
Previewing an existing sheetYou only need to watch the loop, not edit it: sprite animation preview.

Engine exports and how each was checked

"Verified" means the exported files were loaded by the real engine or tool on real CC0 assets and the result was compared with the source. Anything that was not loaded is labelled as such.

TargetFilesCheck
.asepritelayers · blend · opacity · locked · indexed palette · frames · tagsVerified Aseprite 1.3.18The real Aseprite command line reopened the file as INDEXED with 2 layers, Multiply at opacity 160 and the locked layer not editable; the frames are pixel-identical.
Palette files.gpl · .pal · .hex · .ase · .act · .jsonMeasured read backEach format was written and read back identical; Aseprite's own .gpl, .pal, .hex and .act and a real Adobe .ase (16 colours) were read.
Engine bundlesGodot 4 · Unity 6 · Phaser 3/4 · PixiJS 8 … through Pack & ExportVerified Godot 4.7.2 · Unity 6000.5The sprite you drew is packed in Pack & Export; those engine files were loaded in each engine on real CC0 assets (47 of 49 runs pass; the 2 failures are a Phaser 3.90 bug the Phaser 3 preset avoids).
Frame PNG · .nerulioPNG-8 with the palette when an indexed frame fits it · project filePlain filesPlain files for any tool; no engine run is claimed for them.

Evidence

Cleanup benchmark, 69 CC0 cases with a known 1× original: the exact size found for 81 % (±1 px: 90 %) and 74.0 % of pixels exact with the background kept; the best competitor found 30 % of sizes (perfectPixel) and the best exact-pixel score was 44.6 % (unfake.js). An Aseprite-made ×4 nearest upscale came back 100 % exact and a ×3.78 bilinear resize 99.7 %, both with the size found automatically. A 1 px outline and a drop shadow matched Aseprite pixel for pixel (1846 and 705 px). .aseprite files written here reopen in Aseprite 1.3.18 as indexed, with their layers, blend mode and lock.

Limits

  • , and . stop at the first and last frame instead of wrapping; use Enter or loop inside a tag to watch the cycle.
  • No linked cels: a pose used twice is two copies of its pixels.
  • No automatic in-betweening; every frame is drawn.
  • Not in the Pixel workspace (Aseprite has them): layer groups, linked cels, tilemap layers, reference layers, RotSprite and free rotation or scaling of a selection (only flips and 90°), gradient, spray, contour and text tools, custom brushes (square or round, 1–16 px), tiled drawing, Lua scripting and a command line.
  • The symmetry axis is typed, not dragged on the canvas. On touch there is no right button, so the background colour needs the swap button or X.
  • Cleanup: JPEG input comes back close (84 % of pixels within ΔE 0.02 with the background kept) but rarely exact (12 %); simulated generated art gets the exact size 20 % of the time (±1 px: 80 %); none of 7 real generated images was snapped automatically — you type the pixel size the panel suggests.
  • Indexed conversion picks the nearest colour in Oklab, not Aseprite's RGB distance: 68 % of the test sprite's pixels got a different (perceptually closer) pick. The Lospec search is the only network request and asks first.

Versions and sources

Nerulio: new frames default to 100 ms (src/studio/sprite/playback.js); playback of a 512×512, 100-frame sprite kept 100 ms frames on time (20 frames in 2 s) in headless Chromium; the GIF writer rounds to 1/100 s with a 20 ms floor and reports it, and the APNG writer keeps exact milliseconds (src/game/export/anim.js). GIF timing follows the GIF89a specification.

Official documentation used

Questions

Is there a limit on the number of frames?

There is no fixed limit; memory is the bound. The browser test paints a 512×512 sprite of 100 frames, where switching frames took 16 ms and undo 90 to 140 ms.

Do the , and . keys wrap around at the end of the animation?

No. In this version they stop at the first and last frame instead of wrapping, the same in the Sprite workspace. Play with Enter or loop inside a tag to watch the cycle continuously.

Can I export the animation as a GIF?

Yes, through Pack & Export: one GIF or exact APNG per animation, with each frame's own delay. The exported files were decoded again frame by frame; GIF has only on/off transparency, so keep soft edges in APNG.

Is it free, and do I need an account?

Everything on these pages is free and works without an account. There is no watermark and no export that is held back.

Are my sprites uploaded?

No. The Studio decodes, packs and exports in this browser tab. Projects are autosaved in the browser (IndexedDB) and can be saved as a .nerulio file.

Does it need Aseprite or an engine plugin installed?

No. .aseprite files are read and written in the browser, and each engine bundle carries what that engine needs (for Godot, the import settings and scene; for Unity and the Godot TileSet, a small editor script).