Close Menu

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    How to make Solar System Planet Picker Animation using HTML CSS & JavaScript

    10 April 2026

    How to make Glowing Tab Navigation using HTML CSS & JavaScript

    9 April 2026

    How to create Nike Shoes Animation using HTML CSS and JS

    8 April 2026
    Facebook X (Twitter) Instagram YouTube Telegram Threads
    Coding StellaCoding Stella
    • Home
    • Blog
    • HTML & CSS
      • Login Form
    • JavaScript
    • Hire us!
    Coding StellaCoding Stella
    Home - JavaScript - How to create Stick Hero Game using HTML CSS and JS
    JavaScript

    How to create Stick Hero Game using HTML CSS and JS

    Coding StellaBy Coding Stella19 March 2026Updated:22 March 2026No Comments11 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Reddit Email WhatsApp Copy Link

    Let’s create a Stick Hero Game using HTML, CSS, and JavaScript, where you control a character by stretching a stick to reach the next platform with perfect timing.

    We’ll use:

    • HTML to structure the game layout including the canvas, score, instructions, and restart button.
    • CSS to style the game interface, position elements, and create a clean centered layout with smooth UI feedback.
    • JavaScript to handle the full game logic like stick stretching, rotation, hero movement, collision detection, scoring system, and animations using requestAnimationFrame for smooth gameplay.

    This project is perfect for learning how to build interactive canvas-based games, manage game states, and create engaging animations. Whether you’re a beginner or experienced, this Stick Hero Game helps you understand real game mechanics and makes your portfolio more fun and dynamic 🎮🔥

    HTML :

    This HTML sets up a simple Stick Hero game layout where the <canvas> (id=”game”) is the main area where the game is drawn using JavaScript, while elements like #score, #introduction, #perfect, and the RESTART button are used to display game info and controls; the page links an external CSS file for styling (style.css) and a JavaScript file (script.js) that handles all the game logic and interactions like stretching the stick and updating the score.

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <title>Stick Hero Game | @coding.stella</title>
      <link rel="stylesheet" href="./style.css">
    </head>
    
    <body>
    
      <div class="container">
        <div id="score"></div>
        <canvas id="game" width="375" height="375"></canvas>
        <div id="introduction">Hold down the mouse to stretch out a stick</div>
        <div id="perfect">DOUBLE SCORE</div>
        <button id="restart">RESTART</button>
      </div>
    
      <script src="./script.js"></script>
    
    </body>
    
    </html>

    CSS :

    This CSS styles the game UI by making the page full-height with no margins, centering everything using .container with flexbox, and setting a clean font and pointer cursor; it positions the score at the top-right, shows instructions in the center with a fade effect, styles the restart button as a hidden red circular button that appears later, and adds a “perfect” text element with opacity animation for smooth visual feedback during gameplay.

    html,
    body {
      height: 100%;
      margin: 0;
    }
    
    body {
      font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
      cursor: pointer;
    }
    
    .container {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
    }
    
    #score {
      position: absolute;
      top: 30px;
      right: 30px;
      font-size: 2em;
      font-weight: 900;
    }
    
    #introduction {
      width: 200px;
      height: 150px;
      position: absolute;
      font-weight: 600;
      font-size: 0.8em;
      font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
      text-align: center;
      transition: opacity 2s;
    }
    
    #restart {
      width: 120px;
      height: 120px;
      position: absolute;
      border-radius: 50%;
      color: white;
      background-color: red;
      border: none;
      font-weight: 700;
      font-size: 1.2em;
      font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
      display: none;
      cursor: pointer;
    }
    
    #perfect {
      position: absolute;
      opacity: 0;
      transition: opacity 2s;
    }

    JavaScript:

    This JavaScript creates the full Stick Hero game logic using a canvas by managing game states like stretching, turning, walking, and falling, where the player holds the mouse to grow a stick, releases to drop it, and the hero walks across to the next platform if the length is correct; it uses arrays to store platforms, sticks, and trees, generates random elements for each round, updates positions using an animation loop (requestAnimationFrame), checks collisions and “perfect” hits for scoring, and continuously redraws everything including background, hero, platforms, and stick to create smooth gameplay.

    // Extend the base functionality of JavaScript
    Array.prototype.last = function () {
      return this[this.length - 1];
    };
    
    // A sinus function that acceps degrees instead of radians
    Math.sinus = function (degree) {
      return Math.sin((degree / 180) * Math.PI);
    };
    
    // Game data
    let phase = "waiting"; // waiting | stretching | turning | walking | transitioning | falling
    let lastTimestamp; // The timestamp of the previous requestAnimationFrame cycle
    
    let heroX; // Changes when moving forward
    let heroY; // Only changes when falling
    let sceneOffset; // Moves the whole game
    
    let platforms = [];
    let sticks = [];
    let trees = [];
    
    // Todo: Save high score to localStorage (?)
    
    let score = 0;
    
    // Configuration
    const canvasWidth = 375;
    const canvasHeight = 375;
    const platformHeight = 100;
    const heroDistanceFromEdge = 10; // While waiting
    const paddingX = 100; // The waiting position of the hero in from the original canvas size
    const perfectAreaSize = 10;
    
    // The background moves slower than the hero
    const backgroundSpeedMultiplier = 0.2;
    
    const hill1BaseHeight = 100;
    const hill1Amplitude = 10;
    const hill1Stretch = 1;
    const hill2BaseHeight = 70;
    const hill2Amplitude = 20;
    const hill2Stretch = 0.5;
    
    const stretchingSpeed = 4; // Milliseconds it takes to draw a pixel
    const turningSpeed = 4; // Milliseconds it takes to turn a degree
    const walkingSpeed = 4;
    const transitioningSpeed = 2;
    const fallingSpeed = 2;
    
    const heroWidth = 17; // 24
    const heroHeight = 30; // 40
    
    const canvas = document.getElementById("game");
    canvas.width = window.innerWidth; // Make the Canvas full screen
    canvas.height = window.innerHeight;
    
    const ctx = canvas.getContext("2d");
    
    const introductionElement = document.getElementById("introduction");
    const perfectElement = document.getElementById("perfect");
    const restartButton = document.getElementById("restart");
    const scoreElement = document.getElementById("score");
    
    // Initialize layout
    resetGame();
    
    // Resets game variables and layouts but does not start the game (game starts on keypress)
    function resetGame() {
      // Reset game progress
      phase = "waiting";
      lastTimestamp = undefined;
      sceneOffset = 0;
      score = 0;
    
      introductionElement.style.opacity = 1;
      perfectElement.style.opacity = 0;
      restartButton.style.display = "none";
      scoreElement.innerText = score;
    
      // The first platform is always the same
      // x + w has to match paddingX
      platforms = [{ x: 50, w: 50 }];
      generatePlatform();
      generatePlatform();
      generatePlatform();
      generatePlatform();
    
      sticks = [{ x: platforms[0].x + platforms[0].w, length: 0, rotation: 0 }];
    
      trees = [];
      generateTree();
      generateTree();
      generateTree();
      generateTree();
      generateTree();
      generateTree();
      generateTree();
      generateTree();
      generateTree();
      generateTree();
    
      heroX = platforms[0].x + platforms[0].w - heroDistanceFromEdge;
      heroY = 0;
    
      draw();
    }
    
    function generateTree() {
      const minimumGap = 30;
      const maximumGap = 150;
    
      // X coordinate of the right edge of the furthest tree
      const lastTree = trees[trees.length - 1];
      let furthestX = lastTree ? lastTree.x : 0;
    
      const x =
        furthestX +
        minimumGap +
        Math.floor(Math.random() * (maximumGap - minimumGap));
    
      const treeColors = ["#6D8821", "#8FAC34", "#98B333"];
      const color = treeColors[Math.floor(Math.random() * 3)];
    
      trees.push({ x, color });
    }
    
    function generatePlatform() {
      const minimumGap = 40;
      const maximumGap = 200;
      const minimumWidth = 20;
      const maximumWidth = 100;
    
      // X coordinate of the right edge of the furthest platform
      const lastPlatform = platforms[platforms.length - 1];
      let furthestX = lastPlatform.x + lastPlatform.w;
    
      const x =
        furthestX +
        minimumGap +
        Math.floor(Math.random() * (maximumGap - minimumGap));
      const w =
        minimumWidth + Math.floor(Math.random() * (maximumWidth - minimumWidth));
    
      platforms.push({ x, w });
    }
    
    resetGame();
    
    // If space was pressed restart the game
    window.addEventListener("keydown", function (event) {
      if (event.key == " ") {
        event.preventDefault();
        resetGame();
        return;
      }
    });
    
    window.addEventListener("mousedown", function (event) {
      if (phase == "waiting") {
        lastTimestamp = undefined;
        introductionElement.style.opacity = 0;
        phase = "stretching";
        window.requestAnimationFrame(animate);
      }
    });
    
    window.addEventListener("mouseup", function (event) {
      if (phase == "stretching") {
        phase = "turning";
      }
    });
    
    window.addEventListener("resize", function (event) {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      draw();
    });
    
    window.requestAnimationFrame(animate);
    
    // The main game loop
    function animate(timestamp) {
      if (!lastTimestamp) {
        lastTimestamp = timestamp;
        window.requestAnimationFrame(animate);
        return;
      }
    
      switch (phase) {
        case "waiting":
          return; // Stop the loop
        case "stretching": {
          sticks.last().length += (timestamp - lastTimestamp) / stretchingSpeed;
          break;
        }
        case "turning": {
          sticks.last().rotation += (timestamp - lastTimestamp) / turningSpeed;
    
          if (sticks.last().rotation > 90) {
            sticks.last().rotation = 90;
    
            const [nextPlatform, perfectHit] = thePlatformTheStickHits();
            if (nextPlatform) {
              // Increase score
              score += perfectHit ? 2 : 1;
              scoreElement.innerText = score;
    
              if (perfectHit) {
                perfectElement.style.opacity = 1;
                setTimeout(() => (perfectElement.style.opacity = 0), 1000);
              }
    
              generatePlatform();
              generateTree();
              generateTree();
            }
    
            phase = "walking";
          }
          break;
        }
        case "walking": {
          heroX += (timestamp - lastTimestamp) / walkingSpeed;
    
          const [nextPlatform] = thePlatformTheStickHits();
          if (nextPlatform) {
            // If hero will reach another platform then limit it's position at it's edge
            const maxHeroX = nextPlatform.x + nextPlatform.w - heroDistanceFromEdge;
            if (heroX > maxHeroX) {
              heroX = maxHeroX;
              phase = "transitioning";
            }
          } else {
            // If hero won't reach another platform then limit it's position at the end of the pole
            const maxHeroX = sticks.last().x + sticks.last().length + heroWidth;
            if (heroX > maxHeroX) {
              heroX = maxHeroX;
              phase = "falling";
            }
          }
          break;
        }
        case "transitioning": {
          sceneOffset += (timestamp - lastTimestamp) / transitioningSpeed;
    
          const [nextPlatform] = thePlatformTheStickHits();
          if (sceneOffset > nextPlatform.x + nextPlatform.w - paddingX) {
            // Add the next step
            sticks.push({
              x: nextPlatform.x + nextPlatform.w,
              length: 0,
              rotation: 0
            });
            phase = "waiting";
          }
          break;
        }
        case "falling": {
          if (sticks.last().rotation < 180)
            sticks.last().rotation += (timestamp - lastTimestamp) / turningSpeed;
    
          heroY += (timestamp - lastTimestamp) / fallingSpeed;
          const maxHeroY =
            platformHeight + 100 + (window.innerHeight - canvasHeight) / 2;
          if (heroY > maxHeroY) {
            restartButton.style.display = "block";
            return;
          }
          break;
        }
        default:
          throw Error("Wrong phase");
      }
    
      draw();
      window.requestAnimationFrame(animate);
    
      lastTimestamp = timestamp;
    }
    
    // Returns the platform the stick hit (if it didn't hit any stick then return undefined)
    function thePlatformTheStickHits() {
      if (sticks.last().rotation != 90)
        throw Error(`Stick is ${sticks.last().rotation}°`);
      const stickFarX = sticks.last().x + sticks.last().length;
    
      const platformTheStickHits = platforms.find(
        (platform) => platform.x < stickFarX && stickFarX < platform.x + platform.w
      );
    
      // If the stick hits the perfect area
      if (
        platformTheStickHits &&
        platformTheStickHits.x + platformTheStickHits.w / 2 - perfectAreaSize / 2 <
        stickFarX &&
        stickFarX <
        platformTheStickHits.x + platformTheStickHits.w / 2 + perfectAreaSize / 2
      )
        return [platformTheStickHits, true];
    
      return [platformTheStickHits, false];
    }
    
    function draw() {
      ctx.save();
      ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
    
      drawBackground();
    
      // Center main canvas area to the middle of the screen
      ctx.translate(
        (window.innerWidth - canvasWidth) / 2 - sceneOffset,
        (window.innerHeight - canvasHeight) / 2
      );
    
      // Draw scene
      drawPlatforms();
      drawHero();
      drawSticks();
    
      // Restore transformation
      ctx.restore();
    }
    
    restartButton.addEventListener("click", function (event) {
      event.preventDefault();
      resetGame();
      restartButton.style.display = "none";
    });
    
    function drawPlatforms() {
      platforms.forEach(({ x, w }) => {
        // Draw platform
        ctx.fillStyle = "black";
        ctx.fillRect(
          x,
          canvasHeight - platformHeight,
          w,
          platformHeight + (window.innerHeight - canvasHeight) / 2
        );
    
        // Draw perfect area only if hero did not yet reach the platform
        if (sticks.last().x < x) {
          ctx.fillStyle = "red";
          ctx.fillRect(
            x + w / 2 - perfectAreaSize / 2,
            canvasHeight - platformHeight,
            perfectAreaSize,
            perfectAreaSize
          );
        }
      });
    }
    
    function drawHero() {
      ctx.save();
      ctx.fillStyle = "black";
      ctx.translate(
        heroX - heroWidth / 2,
        heroY + canvasHeight - platformHeight - heroHeight / 2
      );
    
      // Body
      drawRoundedRect(
        -heroWidth / 2,
        -heroHeight / 2,
        heroWidth,
        heroHeight - 4,
        5
      );
    
      // Legs
      const legDistance = 5;
      ctx.beginPath();
      ctx.arc(legDistance, 11.5, 3, 0, Math.PI * 2, false);
      ctx.fill();
      ctx.beginPath();
      ctx.arc(-legDistance, 11.5, 3, 0, Math.PI * 2, false);
      ctx.fill();
    
      // Eye
      ctx.beginPath();
      ctx.fillStyle = "white";
      ctx.arc(5, -7, 3, 0, Math.PI * 2, false);
      ctx.fill();
    
      // Band
      ctx.fillStyle = "red";
      ctx.fillRect(-heroWidth / 2 - 1, -12, heroWidth + 2, 4.5);
      ctx.beginPath();
      ctx.moveTo(-9, -14.5);
      ctx.lineTo(-17, -18.5);
      ctx.lineTo(-14, -8.5);
      ctx.fill();
      ctx.beginPath();
      ctx.moveTo(-10, -10.5);
      ctx.lineTo(-15, -3.5);
      ctx.lineTo(-5, -7);
      ctx.fill();
    
      ctx.restore();
    }
    
    function drawRoundedRect(x, y, width, height, radius) {
      ctx.beginPath();
      ctx.moveTo(x, y + radius);
      ctx.lineTo(x, y + height - radius);
      ctx.arcTo(x, y + height, x + radius, y + height, radius);
      ctx.lineTo(x + width - radius, y + height);
      ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
      ctx.lineTo(x + width, y + radius);
      ctx.arcTo(x + width, y, x + width - radius, y, radius);
      ctx.lineTo(x + radius, y);
      ctx.arcTo(x, y, x, y + radius, radius);
      ctx.fill();
    }
    
    function drawSticks() {
      sticks.forEach((stick) => {
        ctx.save();
    
        // Move the anchor point to the start of the stick and rotate
        ctx.translate(stick.x, canvasHeight - platformHeight);
        ctx.rotate((Math.PI / 180) * stick.rotation);
    
        // Draw stick
        ctx.beginPath();
        ctx.lineWidth = 2;
        ctx.moveTo(0, 0);
        ctx.lineTo(0, -stick.length);
        ctx.stroke();
    
        // Restore transformations
        ctx.restore();
      });
    }
    
    function drawBackground() {
      // Draw sky
      var gradient = ctx.createLinearGradient(0, 0, 0, window.innerHeight);
      gradient.addColorStop(0, "#BBD691");
      gradient.addColorStop(1, "#FEF1E1");
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
    
      // Draw hills
      drawHill(hill1BaseHeight, hill1Amplitude, hill1Stretch, "#95C629");
      drawHill(hill2BaseHeight, hill2Amplitude, hill2Stretch, "#659F1C");
    
      // Draw trees
      trees.forEach((tree) => drawTree(tree.x, tree.color));
    }
    
    // A hill is a shape under a stretched out sinus wave
    function drawHill(baseHeight, amplitude, stretch, color) {
      ctx.beginPath();
      ctx.moveTo(0, window.innerHeight);
      ctx.lineTo(0, getHillY(0, baseHeight, amplitude, stretch));
      for (let i = 0; i < window.innerWidth; i++) {
        ctx.lineTo(i, getHillY(i, baseHeight, amplitude, stretch));
      }
      ctx.lineTo(window.innerWidth, window.innerHeight);
      ctx.fillStyle = color;
      ctx.fill();
    }
    
    function drawTree(x, color) {
      ctx.save();
      ctx.translate(
        (-sceneOffset * backgroundSpeedMultiplier + x) * hill1Stretch,
        getTreeY(x, hill1BaseHeight, hill1Amplitude)
      );
    
      const treeTrunkHeight = 5;
      const treeTrunkWidth = 2;
      const treeCrownHeight = 25;
      const treeCrownWidth = 10;
    
      // Draw trunk
      ctx.fillStyle = "#7D833C";
      ctx.fillRect(
        -treeTrunkWidth / 2,
        -treeTrunkHeight,
        treeTrunkWidth,
        treeTrunkHeight
      );
    
      // Draw crown
      ctx.beginPath();
      ctx.moveTo(-treeCrownWidth / 2, -treeTrunkHeight);
      ctx.lineTo(0, -(treeTrunkHeight + treeCrownHeight));
      ctx.lineTo(treeCrownWidth / 2, -treeTrunkHeight);
      ctx.fillStyle = color;
      ctx.fill();
    
      ctx.restore();
    }
    
    function getHillY(windowX, baseHeight, amplitude, stretch) {
      const sineBaseY = window.innerHeight - baseHeight;
      return (
        Math.sinus((sceneOffset * backgroundSpeedMultiplier + windowX) * stretch) *
        amplitude +
        sineBaseY
      );
    }
    
    function getTreeY(x, baseHeight, amplitude) {
      const sineBaseY = window.innerHeight - baseHeight;
      return Math.sinus(x) * amplitude + sineBaseY;
    }

    In conclusion, the Stick Hero Game is a great project to understand how HTML, CSS, and JavaScript work together to create an interactive experience, combining canvas rendering, animations, and user input to simulate real game mechanics 🎮

    If your project has problems, don’t worry. Just click to download the source code and face your coding challenges with excitement. Have fun coding!

    Animation JavaScript Web Development
    Share. Copy Link Twitter Facebook LinkedIn Email WhatsApp
    Previous ArticleHow to make Netflix Login page using HTML & CSS
    Next Article How to create Snake Volume Slider using HTML CSS and JS
    Coding Stella
    • Website

    Related Posts

    JavaScript

    How to make Solar System Planet Picker Animation using HTML CSS & JavaScript

    10 April 2026
    JavaScript

    How to make Glowing Tab Navigation using HTML CSS & JavaScript

    9 April 2026
    JavaScript

    How to create Nike Shoes Animation using HTML CSS and JS

    8 April 2026
    Add A Comment
    Leave A Reply Cancel Reply

    Trending Post

    Master Frontend in 100 Days Ebook

    2 March 202432K Views

    How to make Modern Login Form using HTML & CSS | Glassmorphism

    11 January 202431K Views

    How to make I love you Animation in HTML CSS & JavaScript

    14 February 202424K Views

    How to make Valentine’s Day Card using HTML & CSS

    13 February 202415K Views
    Follow Us
    • Instagram
    • Facebook
    • YouTube
    • Twitter
    ads
    Featured Post

    How to create Animated Fanta Website using HTML CSS and JS

    20 July 2025

    How to make Netflix Login page using HTML & CSS

    18 March 2026

    How to make Sphere Packing Animation using HTML CSS & JavaScript

    7 December 2024

    How to create Magic Indicator Menu using HTML CSS and JS

    20 June 2025
    Latest Post

    How to make Solar System Planet Picker Animation using HTML CSS & JavaScript

    10 April 2026

    How to make Glowing Tab Navigation using HTML CSS & JavaScript

    9 April 2026

    How to create Nike Shoes Animation using HTML CSS and JS

    8 April 2026

    How to Make Animated Login Form in HTML and CSS

    4 April 2026
    Facebook X (Twitter) Instagram YouTube
    • About Us
    • Privacy Policy
    • Return and Refund Policy
    • Terms and Conditions
    • Contact Us
    • Buy me a coffee
    © 2026 Coding Stella. Made with 💙 by @coding.stella

    Type above and press Enter to search. Press Esc to cancel.

    Ad Blocker Enabled!
    Ad Blocker Enabled!
    Looks like you're using an ad blocker. We rely on advertising to help fund our site.
    Okay! I understood