JSFiddle - React, Tailwind, and code Playground

HTML

<div>Smoothing Disabled</div>
    <p>Use the arrow keys to scroll and the D and F keys to scale. The source image is represented on the top left. Notice the vertical and horizontal lines that appear between tiles as you scroll and scale. They are the color of the tile's neighbor in the source image. This may be due to color sampling that occurs during scaling. Click the toggle to set imageSmoothingEnabled on the drawing context.</p>

    <canvas></canvas>

CSS

* { box-sizing:border-box; margin:0; overflow:hidden; padding:0; user-select:none; }

      body, html { background-color:#202830; color:#ffffff; height:100%; width:100%; }

      body { align-items:center; display:grid; justify-items:center; }

      p { max-width:640px; }

      div { border:#ffffff 2px solid; bottom:4px; cursor:pointer; padding:8px; position:fixed; right:4px }

JavaScript

/* This is the tile map. Each value is a frame index in the FRAMES array. Each frame tells drawImage where to blit the source from */
      const MAP = [0,0,0,1,1,1,1,2,2,2,
                   0,1,0,1,2,2,1,2,3,2,
                   0,0,0,1,1,1,1,2,2,2,
                   3,3,3,4,4,4,4,5,5,5,
                   3,4,3,4,5,5,4,5,6,5,
                   3,4,3,4,5,5,4,5,6,5,
                   3,3,3,4,4,4,4,5,5,5,
                   6,6,6,7,7,7,7,8,8,8,
                   6,7,6,7,8,8,7,8,0,8,
                   6,6,6,7,7,7,7,8,8,8];

      const TILE_SIZE = 8; // Each tile is 8x8 pixels

      const MAP_HEIGHT = 80; // The map is 80 pixels tall by 80 pixels wide
      const MAP_WIDTH  = 80;

      /* Each frame represents the source x, y coordinates of a tile in the source image. They are indexed according to the map values */
      const FRAMES = [{ x:0,  y:0 }, // map value = 0
                      { x:8,  y:0 }, // map value = 1
                      { x:16, y:0 }, // map value = 2
                      { x:0,  y:8 }, // etc.
                      { x:8,  y:8 },
                      { x:16, y:8},
                      { x:0,  y:16},
                      { x:8,  y:16},
                      { x:16, y:16}];

      /* These represent the state of the keyboard keys being used. false is up and true is down */
      const KEYS = {

        down:false,
        left:false,
        right:false,
        scale_down:false, // the D key
        scale_up:false,   // the F key
        up:false
        
      }

      /* This is the scroll offset. You can also think of it as the position of the red dot in the map. */
      const OFFSET = { x:MAP_WIDTH * 0.5, y:MAP_HEIGHT * 0.5 }; // It starts out centered in the map.

      const MAX_SCALE = 75; // Max scale is 75 times larger than the actual image size.
      const MIN_SCALE = 0;  // Texture bleeding seems to only occur on upscale, but min scale is 0 in case you want to try it.
      
      var scale = 4.71; // some arbitrary...