Source Code

<div class="aim-app">
  <div class="aim-header">
    <h2>Aim Trainer</h2>
    <div class="stats">
      <div class="stat"><span class="stat-label">Score</span><span class="stat-val" id="stat-score">0</span></div>
      <div class="stat"><span class="stat-label">Combo</span><span class="stat-val" id="stat-combo">x1</span></div>
      <div class="stat"><span class="stat-label">Time</span><span class="stat-val" id="stat-time">30</span></div>
    </div>
  </div>

  <div class="arena" id="arena">
    <div class="overlay" id="overlay">
      <p class="overlay-title">Precision Aim Trainer</p>
      <p class="overlay-text">Targets shrink as they age — hit them dead-center, early, for the most points. Miss the ring entirely and your combo resets.</p>
      <button class="primary-btn" id="start-btn">Start (30s)</button>
    </div>
  </div>

  <p class="result" id="result"></p>
</div>

Precision Aim Trainer Game — Free HTML CSS JS Snippet

Precision Aim Trainer Game · Games · Plain HTML, CSS & JS · Live preview

What's included

Features

Targets shrink continuously via an injected CSS @keyframes animation tied to a randomized per-target lifespan
Hit scoring is proportional to remaining target life at click time — early hits are worth far more than late ones
Combo multiplier (up to x8) rewards consecutive hits and resets hard to x1 on any miss or expiry
activeTargets Set guards against race conditions between a target's expiry timeout and a click event
Continuous unpredictable spawning via randomized 280-620ms delays rather than a fixed grid or pattern
Floating score popups show exact points or a miss label at the precise click location
Fixed 30-second round enforced by setInterval, independent of how many targets were spawned
Bounds-aware random placement keeps every target fully inside the arena regardless of its randomized size

About this UI Snippet

Precision Aim Trainer Game — Time-Decaying Targets, Combo Multipliers and DOM-Based Hit Detection

Screenshot of the Precision Aim Trainer Game snippet rendered live

An aim trainer is a genre of reflex game built around clicking small, often moving or shrinking targets as quickly and accurately as possible. This implementation spawns one target at a time inside a fixed arena, each shrinking visibly as it ages via a CSS keyframe animation, and rewards clicks that land early in a target's lifespan — while a stray click anywhere the target used to be, once it has fully shrunk away, resets a combo multiplier instead of scoring.

Why this differs from a reaction-time or click-speed test

A pure reaction-time tester measures a single interval between a stimulus and one click; a click-speed test measures raw clicks per second against no target at all. This game instead measures *spatial precision under a decaying time budget*: every target has a randomized lifespan (MIN_LIFE to MAX_LIFE milliseconds) during which it visually shrinks from full size to nearly nothing via an injected @keyframes target-shrink animation, and the score for a hit is directly proportional to how early — i.e., how large the target still was — when the click landed. This combines timing pressure with genuine point-and-click accuracy in a way neither a reaction tester nor a click-speed counter does.

Scoring: proportional to remaining life, multiplied by a combo streak

On a target click, performance.now() - born gives the target's exact age in milliseconds, converted to lifeFrac, the fraction of its lifespan remaining (1 for an instant hit, approaching 0 as it's about to expire). Base points scale from that fraction — Math.max(5, Math.round(10 + lifeFrac * 40)) — so a target hit the instant it spawns is worth up to five times more than one hit right before it disappears. Every successful hit also increments a combo multiplier (capped at 8x) that multiplies the base points for that hit and every subsequent one, so a run of consecutive successful hits compounds quickly, while any miss — clicking empty arena space, or a target expiring unclicked — resets combo back to 1x, punishing sloppy or panicked clicking.

Continuous spawning without overlap bugs

Rather than a fixed grid of possible positions, spawnTarget() picks a fully random x/y within the arena bounds (inset by half the target's own size so it never renders partially off-screen) and calls scheduleNextSpawn() at the end of every spawn, which queues the next target after a random 280-620ms delay — creating an unpredictable, continuously-refreshing stream of targets rather than a static pattern a player could memorize. An activeTargets Set tracks every currently-live target DOM node so that a click on a target already removed (e.g. by its own expiry timeout firing in the same tick as a click) is safely ignored via the activeTargets.has(target) guard, preventing double-scoring or errors from a race between the expiry timer and a click handler.

Score popups and a 30-second countdown

Every hit or miss spawns a small floating .popup element showing the exact points earned (green) or the word "miss" (red), animated upward and fading out via a CSS @keyframes pop-fade, giving immediate, readable feedback without interrupting play. A setInterval-driven countdown ticks timeLeft down from 30 seconds; when it reaches zero, endGame() clears all pending timers, removes any live targets, and rebuilds the overlay to show the final score with a "Play again" button, so a full round is always exactly 30 seconds regardless of how many targets were spawned or missed during it.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the score formula turns a target's remaining-life fraction into points, and why the activeTargets Set is necessary to prevent a race condition between a target's expiry timeout and a click event landing in the same animation frame. It is also a good candidate for extension — ask the assistant to add multiple simultaneous targets instead of one at a time for a harder mode, a moving-target variant where targets drift across the arena instead of staying fixed once spawned, or persistent high-score tracking via localStorage so a returning player has a personal best to beat.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a precision aim-trainer mini-game in plain HTML, CSS, and JavaScript — no libraries, no canvas, using plain DOM elements positioned absolutely inside a fixed arena.

Requirements:
- Spawn one circular target at a time at a random position fully inside the arena bounds (accounting for its own randomized size so it never renders partially outside), and animate it shrinking continuously from full size toward nearly nothing over a randomized lifespan using a CSS keyframe animation.
- On clicking a target, compute how much of its lifespan remains at the moment of the click and award points proportional to that remaining fraction, so an early hit scores substantially more than a late one; also increment a combo multiplier (capped at a reasonable maximum) that multiplies every hit's points while it is active.
- If a spawned target's lifespan expires without being clicked, or the player clicks empty arena space where no target currently exists, reset the combo multiplier back to its base value of 1.
- Guard against a target being processed twice if its expiry timer and a click event could both fire around the same time — track currently-live targets in a data structure and check membership before awarding points or removing a target a second time.
- Continuously schedule the next target spawn after a short randomized delay following each spawn, so targets appear in an unpredictable but roughly steady stream rather than a fixed pattern.
- Show a brief floating score or "miss" label at the exact click location that fades out, run the whole game on a fixed 30-second countdown, and show a final-score summary with a replay button once the timer reaches zero.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Click Start to begin a 30-second runstartGame() resets score, combo, and the countdown, then calls spawnTarget() to place the first shrinking target in the arena.
  2. 2
    Click targets as early as possibleEach target shrinks continuously from full size toward nothing over its randomized lifespan. Points scale with how much of that lifespan remains when you click — earlier hits score far more.
  3. 3
    Build and protect your comboConsecutive successful hits raise a combo multiplier up to x8, multiplying every subsequent hit's points. Any miss — clicking empty space or letting a target expire — resets the combo to x1.
  4. 4
    Watch the score popupsEvery click spawns a floating green "+points" or red "miss" label at the click location so you can read exactly how each hit scored without checking the header stats.
  5. 5
    Play until the timer hits zeroA live countdown in the header ticks down from 30. When it reaches 0, endGame() stops spawning, clears the arena, and shows your final score with a Play again button.
  6. 6
    Tune the difficultyAdjust MIN_LIFE/MAX_LIFE for how long targets last, MIN_SIZE/MAX_SIZE for their size range, and the 280-620ms range in scheduleNextSpawn() for how densely targets appear.

Real-world uses

Common Use Cases

Standalone reflex and precision practice tool
A focused practice loop for click precision and timing under pressure, distinct from a pure reaction time tester or click speed test that do not require spatial targeting.
Warm-up mini-game for a gaming or esports-adjacent site
Embed as a quick skill-check widget before a competitive game session, similar in spirit to dedicated aim-trainer tools used by first-person-shooter players to warm up.
Teaching example for timing-based DOM animation and race conditions
The interplay between a CSS keyframe animation, a JS expiry timeout, and a click handler racing against both is a compact, realistic example of coordinating animation state with event handling.
Score popup and combo-multiplier UI pattern reference
The floating, auto-fading popup technique and the combo-multiplier scoring display are directly reusable in other arcade-style mini-games or gamified interaction patterns.
Loading-screen or empty-state engagement filler
Small and fully self-contained with no backend or external assets, this drops cleanly into an idle moment in a product as an optional, skippable distraction.

Got questions?

Frequently Asked Questions

On click, the code computes the target's age in milliseconds since it spawned (via performance.now() - born), converts that into a 0-to-1 remaining-life fraction, and derives base points as Math.max(5, Math.round(10 + lifeFrac * 40)) — so a near-instant hit can score close to 50 base points while a last-moment hit scores closer to the 5-point floor. That base amount is then multiplied by the current combo.

Two things reset combo to 1: clicking anywhere in the arena that is not an active target (handled by onArenaMiss, which checks e.target === arena), and letting any spawned target's expiry setTimeout fire because it was never clicked in time.

A target can be removed by two independent triggers — a user click or its own expiry timeout — that could both fire in quick succession. Checking activeTargets.has(target) before processing a click guarantees a target already removed by its expiry timer is never double-processed or scored after the fact.

spawnTarget() insets the random x/y range by half of that specific target's randomized size (plus a small margin) on every side, using rand(size / 2 + 6, rect.width - size / 2 - 6) and the equivalent for y, so even the largest possible target never renders partially outside the arena bounds.

Lower MIN_LIFE and MAX_LIFE to make targets shrink and expire faster, reduce MIN_SIZE and MAX_SIZE for smaller, harder-to-hit targets, or tighten the random delay range inside scheduleNextSpawn() (currently 280-620ms) to spawn targets more densely and force faster target-switching.

Yes — target and arena click handlers respond to standard click events, which fire on tap for touch devices without any additional touch-specific event listeners needed, though very fast successive taps may benefit from adding pointerdown handling for lower input latency.