<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: footballbros.me operator</title>
    <description>The latest articles on DEV Community by footballbros.me operator (@footballbrosme).</description>
    <link>https://dev.to/footballbrosme</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4211927%2F24ca6fb6-f6f4-48e3-9d31-637d7a83b2ab.png</url>
      <title>DEV Community: footballbros.me operator</title>
      <link>https://dev.to/footballbrosme</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/footballbrosme"/>
    <language>en</language>
    <item>
      <title>Lessons from Running a Small HTML5 Sports-Game Site: Embedding, Performance and Mobile Controls</title>
      <dc:creator>footballbros.me operator</dc:creator>
      <pubDate>Wed, 28 Oct 2026 01:47:21 +0000</pubDate>
      <link>https://dev.to/footballbrosme/lessons-from-running-a-small-html5-sports-game-site-embedding-performance-and-mobile-controls-56k7</link>
      <guid>https://dev.to/footballbrosme/lessons-from-running-a-small-html5-sports-game-site-embedding-performance-and-mobile-controls-56k7</guid>
      <description>&lt;p&gt;Hosting browser games looks simple: drop an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; on a page and you're done. In practice, a game frame is one of the heaviest, most input-hungry things you can put in a document, and it fights with the page around it in surprising ways.&lt;/p&gt;

&lt;p&gt;On &lt;a href="https://footballbros.me/" rel="noopener noreferrer"&gt;football bros&lt;/a&gt;, our site that hosts the game Football Bros along with roughly 35 other HTML5 football and soccer games, we've run into most of those fights. A quick disclaimer up front: we didn't make Football Bros. It's developed by Blue Wizard Digital (official site: footballbros.io), and we're an independent site with no affiliation. Our job is the boring-but-important layer around the games: embedding, page performance, input handling and keeping things usable on phones.&lt;/p&gt;

&lt;p&gt;Here's what we've learned, with code you can reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Embed third-party games defensively
&lt;/h2&gt;

&lt;p&gt;A game iframe runs someone else's code. Treat it that way.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;sandbox&lt;/code&gt; attribute starts from "deny everything" and lets you opt back in to specific capabilities. Most HTML5 games need scripts and, frequently, same-origin access to their own storage (for save data or settings). Popups and top-level navigation are rarely needed and are a common source of unwanted redirects.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;allow&lt;/code&gt; attribute (Permissions Policy) controls powerful features such as fullscreen, autoplay and gamepad access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"game-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"game-frame"&lt;/span&gt;
    &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Football Bros – browser game"&lt;/span&gt;
    &lt;span class="na"&gt;data-src=&lt;/span&gt;&lt;span class="s"&gt;"https://example-game-host.com/football-bros/index.html"&lt;/span&gt;
    &lt;span class="na"&gt;sandbox=&lt;/span&gt;&lt;span class="s"&gt;"allow-scripts allow-same-origin allow-pointer-lock"&lt;/span&gt;
    &lt;span class="na"&gt;allow=&lt;/span&gt;&lt;span class="s"&gt;"fullscreen; autoplay; gamepad"&lt;/span&gt;
    &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;
    &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
    &lt;span class="na"&gt;referrerpolicy=&lt;/span&gt;&lt;span class="s"&gt;"strict-origin-when-cross-origin"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"play-btn"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Play&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;allow-scripts&lt;/code&gt; + &lt;code&gt;allow-same-origin&lt;/code&gt; together&lt;/strong&gt; effectively let a same-origin frame remove its own sandbox. That's only acceptable when the game is served from a &lt;em&gt;different&lt;/em&gt; origin than your site, which is one more reason to keep game files on a separate domain or subdomain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;allow-popups&lt;/code&gt; only if you need it.&lt;/strong&gt; Some games link out to their developer's site; if you allow popups, consider &lt;code&gt;allow-popups-to-escape-sandbox&lt;/code&gt; carefully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always set a &lt;code&gt;title&lt;/code&gt;.&lt;/strong&gt; Screen readers announce it, and it's the only description an iframe gets.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allowfullscreen&lt;/code&gt; is the legacy attribute; &lt;code&gt;allow="fullscreen"&lt;/code&gt; is the modern one. Including both costs nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Don't load the game until someone wants to play
&lt;/h2&gt;

&lt;p&gt;A game bundle can easily be several megabytes of JavaScript, WebAssembly, audio and textures. If it loads on page load, it competes with everything else for bandwidth and main-thread time, which hurts Largest Contentful Paint (LCP) and Interaction to Next Paint (INP).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;loading="lazy"&lt;/code&gt; on iframes helps for frames below the fold, but on a game page the frame is usually &lt;em&gt;above&lt;/em&gt; the fold, so the browser loads it immediately anyway. The more effective pattern is &lt;strong&gt;click-to-play&lt;/strong&gt;: render a lightweight poster image and a button, and only set the iframe's &lt;code&gt;src&lt;/code&gt; when the user asks for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.game-wrapper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;game-frame&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.play-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;playBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// start loading the game now&lt;/span&gt;
  &lt;span class="nx"&gt;playBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                   &lt;span class="c1"&gt;// give keyboard input to the game&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Optional fullscreen toggle using the Fullscreen API&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toggleFullscreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullscreenElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestFullscreen&lt;/span&gt;&lt;span class="p"&gt;?.().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exitFullscreen&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits we've seen in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The poster image becomes the LCP element, and it's small and cacheable.&lt;/li&gt;
&lt;li&gt;Visitors who only came to read instructions or browse other games don't pay the download cost.&lt;/li&gt;
&lt;li&gt;Audio autoplay policies stop being a problem, because the game starts after a user gesture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Call &lt;code&gt;requestFullscreen()&lt;/code&gt; on the &lt;strong&gt;wrapper&lt;/strong&gt;, not the iframe, if you want your own overlay controls (like an exit button) to stay visible. Note that iOS Safari has historically had limited Fullscreen API support for non-video elements, so always feature-detect and keep a usable non-fullscreen layout. MDN's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API" rel="noopener noreferrer"&gt;Fullscreen API guide&lt;/a&gt; is the best reference for the current quirks.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Stop arrow keys and Space from scrolling the page
&lt;/h2&gt;

&lt;p&gt;This is the number one complaint on any game page: you press the down arrow to move a player and the whole page scrolls instead.&lt;/p&gt;

&lt;p&gt;When the iframe has focus, key events go to the game document, and the game should call &lt;code&gt;preventDefault()&lt;/code&gt; itself. But focus is fragile. Users click outside the frame, tab around, or the game never grabs focus to begin with. Then arrow keys and Space go to &lt;em&gt;your&lt;/em&gt; page and scroll it.&lt;/p&gt;

&lt;p&gt;Two mitigations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Focus the iframe on play&lt;/strong&gt; (shown above), and re-focus it when the user clicks the game area.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suppress scroll keys on the host page while a game is active&lt;/strong&gt;, but never while the user is typing in a form field.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCROLL_KEYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowUp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowDown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowLeft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ArrowRight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PageUp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PageDown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;gameActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;playBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;gameActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;gameActive&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;SCROLL_KEYS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isContentEditable&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INPUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEXTAREA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;typing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be deliberate here. Hijacking Space and arrows on a page breaks keyboard scrolling for people who rely on it. Only enable it once a game is actually running, and give users an obvious way out (an "Exit game" button that resets &lt;code&gt;gameActive&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Touch input: &lt;code&gt;touch-action&lt;/code&gt; and responsive containers
&lt;/h2&gt;

&lt;p&gt;On mobile, the default browser behaviors that hurt games are swipe-to-scroll, pinch-zoom and double-tap-zoom. Inside the game's own document, &lt;code&gt;touch-action: none&lt;/code&gt; on the canvas is the game developer's job. On the host page, you can still make the frame area behave better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.game-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;960px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c"&gt;/* reserves space: no layout shift */&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;#111&lt;/span&gt; &lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;poster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;webp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;cover&lt;/span&gt; &lt;span class="nb"&gt;no-repeat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;touch-action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c"&gt;/* no pan/zoom gestures over the game */&lt;/span&gt;
  &lt;span class="py"&gt;overscroll-behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.game-wrapper&lt;/span&gt; &lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orientation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;portrait&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.game-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;max-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="n"&gt;svh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;aspect-ratio&lt;/code&gt; property replaced the old &lt;code&gt;padding-top: 56.25%&lt;/code&gt; hack and, more importantly, reserves the frame's space before anything loads. That keeps Cumulative Layout Shift (CLS) near zero even when the game boots late. Different games ship with different native ratios, so we store a ratio per game rather than forcing 16:9 everywhere.&lt;/p&gt;

&lt;p&gt;Also: if a game only works with a keyboard, say so on the page. A mobile visitor discovering that after a 10 MB download is a bad experience, and it's a cheap fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Cache game assets aggressively (and correctly)
&lt;/h2&gt;

&lt;p&gt;Game files are ideal for long-lived caching, as long as filenames are versioned (&lt;code&gt;game.3f9a1c.js&lt;/code&gt;). A reasonable setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hashed assets&lt;/strong&gt; (JS, WASM, sprites, audio): &lt;code&gt;Cache-Control: public, max-age=31536000, immutable&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The game's &lt;code&gt;index.html&lt;/code&gt;&lt;/strong&gt; and any unhashed loader: &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; (revalidate every time, so updates roll out).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WASM&lt;/strong&gt; must be served as &lt;code&gt;application/wasm&lt;/code&gt; for streaming compilation to work.&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Brotli or gzip&lt;/strong&gt; for JS, JSON and WASM, but skip it for already-compressed formats like PNG, WebP, MP3 and OGG.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A CDN in front of the game origin makes a huge difference for international visitors, and putting games on a separate host also keeps their heavy assets from sharing a connection pool with your page's critical resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Basic accessibility still applies
&lt;/h2&gt;

&lt;p&gt;Games themselves vary widely in accessibility, and you can't fix third-party code. You &lt;em&gt;can&lt;/em&gt; make the surrounding page solid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a real &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; for Play and Fullscreen, not a clickable &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Give each iframe a descriptive &lt;code&gt;title&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Write out the controls as text ("Arrow keys to move, Space to kick") instead of only showing an image.&lt;/li&gt;
&lt;li&gt;Make sure focus is visible and that users can tab &lt;em&gt;out&lt;/em&gt; of the game area.&lt;/li&gt;
&lt;li&gt;Respect &lt;code&gt;prefers-reduced-motion&lt;/code&gt; in your own page animations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Most of the work in hosting HTML5 games isn't about the games. It's about making a heavy, third-party, input-hungry iframe coexist with a normal web page. Sandbox it, load it on demand, reserve its space, manage focus and scroll keys carefully, cache its assets well, and keep the page around it accessible. None of this is exotic, but getting it all right is the difference between a page that feels broken and one that just lets people play.&lt;/p&gt;

&lt;p&gt;If you host embedded games or interactive widgets yourself, I'd love to hear how you handle focus and mobile input in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>gamedev</category>
      <category>html</category>
    </item>
  </channel>
</rss>
