title: SVG viewbox showing showing off-screen items
body: <p>I am making a game in HTML5 using the the <code>svg</code> tag for the graphics to provide multi-resolution displays. The majority of the game is complete, but in testing I have just come across a major bug which involves the SVG objects being visible, despite being outside of the viewbox on non-native resolutions. I am not sure if this is a flaw in my code, or with the browser itself (Google Chrome 54.0.2840.99 m (64-bit), and 57.0.2950.0 (Official Build) canary (64-bit)). I am having to use Google Chrome because I am using WebSQL in the project (it is not in many major browsers).</p>
  
  <p>The project is a top down scrolling maze game, with shadows that are drawn via raycast filled polygons from shape vertices. The shadows are only drawn if one or more the vertices they are "attached" to are on the screen. The issue is that when I lower my resolution (my default is 1366 x 768) then they do not draw to the top/bottom of the screen as they normally do.<a href="https://i.stack.imgur.com/Jk0KM.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/Jk0KM.png" alt="bad SVG"></a> It should look like this.<a href="https://i.stack.imgur.com/ZcEkW.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/ZcEkW.png" alt="good SVG"></a></p>
  
  <p>Below is a majorly cut-down version of my code. I would assume that if there were an error, in my code, it would be inside the <code>shadowrays()</code> function. Normally, the program would be run in fullscreen mode (F11) to ensure that it is the same size as the window. To move the screen, use the w, a, s and d keys</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>window.setInterval(function() {
    if (draw) {
      if (keystate[controls[2]]) {
        xvelocity -= 2;
      }
      if (keystate[controls[3]]) {
        xvelocity += 2;
      }
      xvelocity = xvelocity * 0.93;
      xscroll += xvelocity;
      svg.setAttribute("viewBox", (xscroll + " " + yscroll + " " + dimensions.width + " " + dimensions.height));
      centerplayer();
      if (checkcollision()) {
        xscroll -= xvelocity;
        xvelocity = xvelocity * -0.8;
      }
      if (keystate[controls[0]]) {
        yvelocity += 2;
      }
      if (keystate[controls[1]]) {
        yvelocity -= 2;
      }
      yvelocity = yvelocity * 0.93;
      yscroll -= yvelocity;
      svg.setAttribute("viewBox", (xscroll + " " + yscroll + " " + dimensions.width + " " + dimensions.height));
      centerplayer();
      if (checkcollision()) {
        yscroll += yvelocity;
        yvelocity = yvelocity * -0.8;
      }
      shadowrays();
    }
  }, 16.666666666666667);
  var keystate = {};
  window.addEventListener("keydown", function(e) {
    if (!(((e.keycode || e.which) &gt; 111) &amp;&amp; ((e.keyCode || e.which) &lt; 124))) {
      keystate[e.keycode || e.which] = true;
    }
  }, true);
  window.addEventListener("keyup", function(e) {
    keystate[e.keycode || e.which] = false;
  }, true);
  
  function shadowrays() {
    var kill = document.getElementsByClassName("shadowray");
    while (kill[0]) {
      kill[0].parentNode.removeChild(kill[0]);
    }
    var len = level.length,
      shape = {},
      points = [],
      poly = [],
      p1x, p2x, p1y, p2y, p3x, p3y, p4x, p4y, p5x, p5y, p6x, p6y, cx = player.cx.animVal.value,
      cy = player.cy.animVal.value,
      m, i, n;
    for (i = 1; i &lt; len; i += 1) {
      shape = level[i];
      points = [shape.x, shape.y, shape.x, shape.y + shape.height, shape.x + shape.width, shape.y + shape.height, shape.x + shape.width, shape.y];
      for (n = 0; n &lt; 4; n += 1) {
        p1x = points[(n * 2) % 8];
        p2x = points[(n * 2 + 2) % 8];
        p1y = points[(n * 2 + 1) % 8];
        p2y = points[(n * 2 + 3) % 8];
        if ((((Math.abs(p1x - cx) &lt;= dimensions.width / 2) || (Math.abs(p2x - cx) &lt;= dimensions.width / 2)) &amp;&amp; ((Math.abs(p1y - cy) &lt;= dimensions.height / 2) || (Math.abs(p2y - cy) &lt;= dimensions.height / 2))) &amp;&amp; shape.id != "enclosure") {
          m = (p1y - cy) / (p1x - cx);
          p6y = m * (cx + dimensions.width / 2 * (Math.abs(p1x - cx) / (p1x - cx))) - m * cx + cy;
          if (Math.abs(p6y - cy) &lt;= dimensions.height / 2) {
            p6x = (p6y - cy) / m + cx;
            p5x = p6x;
            p5y = cy + dimensions.height / 2 * (Math.abs(p6y - cy) / (p6y - cy));
          } else {
            p6y = cy + dimensions.height / 2 * (Math.abs(p6y - cy) / (p6y - cy));
            p6x = (p6y - cy) / m + cx;
            p5x = cx + dimensions.width / 2 * (Math.abs(p6x - cx) / (p6x - cx));
            p5y = p6y;
          }
          m = (p2y - cy) / (p2x - cx);
          p3y = m * (cx + dimensions.width / 2 * (Math.abs(p2x - cx) / (p2x - cx))) - m * cx + cy;
          if (Math.abs(p3y - cy) &lt;= dimensions.height / 2) {
            p3x = (p3y - cy) / m + cx;
            p4x = p3x;
            p4y = cy + dimensions.height / 2 * (Math.abs(p3y - cy) / (p3y - cy));
          } else {
            p3y = cy + dimensions.height / 2 * (Math.abs(p3y - cy) / (p3y - cy));
            p3x = (p3y - cy) / m + cx;
            p4x = Number(cx) + Number(dimensions.width) / 2 * (Math.abs(p3x - cx) / (p3x - cx));
            p4y = p3y;
          }
          poly = [];
          poly.push(p1x + ',' + p1y, p2x + ',' + p2y, p3x + ',' + p3y, p4x + ',' + p4y, p5x + ',' + p5y, p6x + ',' + p6y);
          createpolygon(poly, "", ("s" + i + n), "shadowray");
        }
      }
    }
  }
  
  function createpolygon(points, filter, id, classt) {
    var polygon = document.createElementNS(svgns, "polygon"),
      data_points = "",
      max = points.length,
      z;
    polygon.setAttributeNS(null, "id", "p" + id);
    for (z = 0; z &lt; max; z += 1) {
      data_points += points[z] + " ";
    }
    polygon.setAttributeNS(null, "points", data_points);
    if (filter !== "") {
      polygon.setAttributeNS(null, "filter", filter);
    }
    polygon.setAttributeNS(null, "class", classt);
    svg.appendChild(polygon);
  }
  
  function centerplayer() {
    player.setAttributeNS(null, "cx", (xscroll + dimensions.width / 2));
    player.setAttributeNS(null, "cy", (yscroll + dimensions.height / 2));
    var hitbox = document.getElementById("rhb");
    hitbox.setAttributeNS(null, "width", player.r.animVal.value * 2);
    hitbox.setAttributeNS(null, "height", player.r.animVal.value * 2);
    //            var hitboxbbox = hitbox.getBBox();
    hitbox.setAttributeNS(null, "transform", ("translate(" + (xscroll + dimensions.width / 2 - player.r.animVal.value) + ", " + (yscroll + dimensions.height / 2 - player.r.animVal.value) + ")"));
  }
  
  function draw(lvl) {
    var objects = lvl.length,
      i;
    xscroll = Number(lvl[0].xspawn);
    yscroll = Number(lvl[0].yspawn);
    xvelocity = 0;
    yvelocity = 0;
    for (i = 1; i &lt; objects; i += 1) {
      var object = lvl[i];
      if (object.type == "rect") {
        createrectangle(object.x, object.y, object.width, object.height, "", object.id, object.class);
      } else if (object.type == "circle") {
        createcircle(object.x, object.y, object.r, "", object.id, object.class);
      } else if (object.type == "polygon") {
        createpolygon(object.points, "", object.id, object.class);
      }
    }
    createcircle("7680", "4320", "50", "", "player", "st1");
    createrectangle("0", "0", "100", "100", "", "hb", "st2");
    createrectangle(lvl[0].xexit - 50, lvl[0].yexit - 50, "100", "100", "", "exit", "exit");
    drawn = true;
  }
  
  function createcircle(posx, posy, radius, filter, id, classt) {
    var circle = document.createElementNS(svgns, "circle");
    circle.setAttributeNS(null, "id", "c" + id);
    circle.setAttributeNS(null, "cx", posx);
    circle.setAttributeNS(null, "cy", posy);
    circle.setAttributeNS(null, "r", radius);
    if (filter !== "") {
      circle.setAttributeNS(null, "filter", filter);
    }
    circle.setAttributeNS(null, "class", classt);
    svg.appendChild(circle);
  }
  
  function createrectangle(posx, posy, width, height, filter, id, classt) {
    var rectangle = document.createElementNS(svgns, "rect");
    rectangle.setAttributeNS(null, "id", "r" + id);
    rectangle.setAttributeNS(null, "x", posx);
    rectangle.setAttributeNS(null, "y", posy);
    rectangle.setAttributeNS(null, "width", width);
    rectangle.setAttributeNS(null, "height", height);
    if (filter !== "") {
      rectangle.setAttributeNS(null, "filter", filter);
    }
    rectangle.setAttributeNS(null, "class", classt);
    svg.appendChild(rectangle);
  }
  
  var svg = document.getElementById("canvas"), //the svg element
    dimensions = {
      width: 1920,
      height: 1080
    }, //view dimensions
    svgns = "http://www.w3.org/2000/svg", //the SVG name space address
    player, //player is used later in game
    level, xvelocity, yvelocity, xscroll, yscroll, //current level, speeds and positions
    controls = ["87", "83", "65", "68", "32"],
    drawn = false;
  
  function checkcollision() {
    var r = player.r.animVal.value,
      frame = document.getElementById("renclosure").getBBox(),
      i;
    if ((player.cx.animVal.value &lt; r) || (player.cy.animVal.value &lt; r) || (player.cx.animVal.value &gt; frame.width - r) || (player.cy.animVal.value &gt; frame.height - r)) {
      return true;
    }
    var collisions = [],
      len, r0 = document.getElementById("rhb").getBoundingClientRect(),
      r1 = svg.createSVGRect();
    r1.x = r0.left;
    r1.y = r0.top;
    r1.width = r0.width;
    r1.height = r0.height;
    var collisions_node = svg.getIntersectionList(r1, null);
    len = collisions_node.length;
    for (i = 0; i &lt; len; i += 1) {
      collisions.push(collisions_node[i]);
    }
    for (i = len - 1; i &gt;= 0; i -= 1) {
      if (collisions[i].id == "renclosure" || collisions[i].id == "cplayer" || collisions[i].id == "rpain" || collisions[i].id == "rhb" || collisions[i].className.animVal == "shadowray") {
  
        collisions.splice(i, 1);
      } else if (collisions[i].id == "rexit") { //winner
        //DO STUFF
      }
    }
    if (collisions.length &gt; 0) {
      return true;
    } else {
      return false;
    }
  }
  
  level = [{
    "id": "l0",
    "name": "pretty", //level name
    "description": "oooooo shadows", //desciption
    "xspawn": 0, //where the player starts
    "yspawn": 0,
    "xexit": 2200, //where the exit it
    "yexit": 2200,
    "bits": 50 //umber of qbits in the level
  }, {
    id: "enclosure", //shape id
    type: "rect", //shape type
    x: 0, //top left x
    y: 0, //top left y
    class: "obstacle_box", //class for style
    width: 5760, //width
    height: 2160 //height
  }, {
    id: "1",
    type: "rect",
    x: 150,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "2",
    type: "rect",
    x: 350,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "3",
    type: "rect",
    x: 550,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "4",
    type: "rect",
    x: 750,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "5",
    type: "rect",
    x: 950,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "6",
    type: "rect",
    x: 1150,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "7",
    type: "rect",
    x: 1350,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "8",
    type: "rect",
    x: 1550,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "9",
    type: "rect",
    x: 1750,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "10",
    type: "rect",
    x: 1950,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "11",
    type: "rect",
    x: 2150,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "12",
    type: "rect",
    x: 2350,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "13",
    type: "rect",
    x: 2550,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "14",
    type: "rect",
    x: 2750,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "15",
    type: "rect",
    x: 2950,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "16",
    type: "rect",
    x: 3150,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "17",
    type: "rect",
    x: 3350,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "18",
    type: "rect",
    x: 3550,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "19",
    type: "rect",
    x: 3750,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }, {
    id: "20",
    type: "rect",
    x: 3950,
    y: 200,
    class: "obstacle_box",
    width: 100,
    height: 100
  }];
  
  draw(level);
  player = document.getElementById("cplayer");
  svg.setAttribute("viewBox", (xscroll + " " + yscroll + " " + dimensions.width + " " + dimensions.height));
  centerplayer();
  shadowrays();</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>html,
  body {
    /*default font for all elements*/
    font-family: Calibri;
    /*ensures page is flush*/
    margin: 0px;
    width: 100%;
    height: 100%;
    /*default position of text*/
    text-align: center;
    /*gets rid of scrollbars*/
    overflow: hidden;
    /*cursor is always the normal pointer*/
    cursor: default;
  }
  #canvas {
    /*makes it flush with the page*/
    height: 100%;
    width: 100%;
    padding: 0;
    /*black background*/
    background-color: #000;
    /*more flushing*/
    position: fixed;
    left: 0px;
    top: 0px;
    border: none;
  }
  #border {
    height: 100%;
    width: 100%;
    padding: 0;
    /*black background*/
    background: none;
    /*more flushing*/
    position: fixed;
    left: 0px;
    top: 0px;
    border: 1px solid #000;
  }
  /*currently the player*/
  
  .st1 {
    /*it's black and no outline*/
    fill: black;
    stroke: none;
  }
  /*the main black bits of the levels that are collidable*/
  
  .obstacle_box {
    /*black fill*/
    fill: #000;
    /*black outline*/
    stroke: #000;
    stroke-width: 1;
    /*prevents corner curling*/
    stroke-miterlimit: 10;
  }
  /*the hitbox thingy*/
  
  .st2 {
    /*no fill but darkish outline*/
    fill: none;
    stroke: #999;
    stroke-width: 1;
    stroke-miterlimit: 10;
  }
  /*the exit*/
  
  .exit {
    /*greenish fill*/
    fill: #0C3;
    /*greener outline*/
    stroke: #00FF40;
    stroke-width: 5;
    /*makes the line ends round*/
    stroke-linecap: round;
    /*makes them dashed*/
    stroke-dasharray: 10, 10;
    /*animates in a loop the outline*/
    animation: exit 0.5s linear infinite;
  }
  /*outline effect for the exit*/
  
  @keyframes exit {
    0% {
      /*outline breaks offset*/
      stroke-dashoffset: 0
    }
    100% {
      stroke-dashoffset: 20
    }
  }
  .shadowray {
    /*black*/
    fill: #000;
    /*black*/
    stroke: #000;
    stroke-width: 1;
    stroke-miterlimit: 10;
    /*visible*/
    opacity: 1;
  }
  /*the edge of the level*/
  
  #renclosure {
    /*the background you actually see*/
    fill: #EEE;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;svg id="canvas" viewBox="0 0 1920 1080"&gt;
    &lt;!--for adding SVG filters to--&gt;
    &lt;defs id="filters"&gt;&lt;/defs&gt;
  &lt;/svg&gt;
  &lt;div id="border"&gt;&lt;/div&gt;</code></pre>
  </div>
  </div>
  </p>