2024-12-24 03:00:00
This is part four of my series of posts describing how I made my quiz game o(m)g:image.
The design of the game is simple:
My first thought is to make each question a <form>
with some <input>
radios, e.g.
<img src="question-1.jpg">
<form action="/questions/1/">
<label>
<input type="radio" name="answer" value="1">
First answer
</label>
<label>
<input type="radio" name="answer" value="2">
Second answer
</label>
<!-- the other 2 questions -->
</form>
That makes a lot of sense for a quiz, but I realize that it necessitates a URL structure like this:
/questions/1
/questions/1?answer=1
Why search params? Because that’s how a native HTML <form>
submission works by default.
But I don’t want that because it requires either 1) something other than a static file server, or 2) client-side JavaScript.
With this project, I know what I don’t want:
/questions/1?answer=1
and respond with the appropriate HTML_redirects
file to re-route /questions/1?answer=1
to the file /questions/1/answers/1.html
on the server/questions/1?answer=1
and re-route it to /questions/1/answers/1
So what do I do? I use the same technology used on the SpaceJam website from 1996 to present a quiz: links!
<img src="question-1.jpg">
<a href="questions/1/answers/1">First answer</a>
<a href="questions/1/answers/2">Second answer</a>
<!-- other 2 answers -->
This allows me to have a URL structure like this:
/questions/1
/questions/1/answers/1
Which allows for:
Form submissions as well as links can be navigations.
It’s boring — almost feels unworthy of a blog post — but it’s solid approach to making a website that will require very little intervention in the coming years/decades. Hopefully this quiz lasts as long as the SpaceJam one from 1996.
Reply via: Email :: Mastodon :: Bluesky
Tagged in: #omgImg
Related posts linking here: (2024) Making o(m)g:image, Part I: Design Iterations ::(2024) Making o(m)g:image, Part II: As Little JS As Possible ::(2024) Making o(m)g:image, Part III: The HTML
2024-12-19 03:00:00
This is part three of my series of posts describing how I made my quiz game o(m)g:image.
o(m)g:image is presented like a quiz:
To do this, I generate an HTML page for every question and for every possible question’s answer (correct or incorrect).
In my source code I have a file that outlines all the questions and their answers, e.g.
- id: 1
answerId: 2
answersById:
1: The Ultimate Guide to Developer Happiness
2: The Key to Building High-Performing Teams
3: Woman Wins $500,000 at Georgia/Alabama Football Game
4: A Guide to Sizing Wedding Rings
- id: 2
# more data here
I use Web Origami as my build tool to loop over all this data and output an HTML page for 1) each question, and 2) each answer for each question. The URLs for accessing these pages follow this pattern:
/questions/:question-id/answers/:answer-id
So, for example, question 1 has 4 possible answers and answer number 2 (in the source data) is the correct one. So for that question, I create an HTML page for:
/questions/1
/questions/1/answers/1
, /questions/1/answers/3
, and /questions/1/answers/4
/questions/1/answers/2
Because the game is structured this way, I have a pretty good idea how people will flow through the game:
So when a user lands on a question page, e.g.
/questions/1
And they click on an answer, rather than go fetch that page with JavaScript and insert it into the current page, I go ahead and let the browser’s default take over which is to do a full page reload.
However, I optimize for this by preloading the HTML pages where the user will go next. So if they’re on /questions/1
, I preload each answer page for that question:
<!-- /questions/1 -->
<link rel="preload" href="/questions/1/answers/1/" as="fetch">
<link rel="preload" href="/questions/1/answers/2/" as="fetch">
<link rel="preload" href="/questions/1/answers/3/" as="fetch">
<link rel="preload" href="/questions/1/answers/4/" as="fetch">
Then, when they click on an answer, the browser already has that HTML page. It loads fast! For example, here’s a question page and you can see in the Network tab of the Developer Tools how it preloaded each answer’s HTML page.
Similarly, when someone lands on an answer page for a question — like /questions/1/answers/2
— I have a pretty good idea where they’ll go next (the next question!) so I preload that:
<!-- /questions/1/answers/2 -->
<link rel="preload" href="/questions/2/" as="fetch">
Once again, things load fast!
This is possible, in part, because every HTML page is small. Here’s a screenshot of “View source” on the first question:
Because these pages are so small in size, it’s totally negligible to just load four or five of them in anticipation of a click.
Side note: I considered inlining all my CSS as a <style>
tag for each HTML page. But then as I thought about how I wanted to preload answers, I realized using a <link>
to a single CSS stylesheet across all pages would be even better, because while it’s one extra request it gets cached for every subsequent HTML page — trade-offs! That’s, in part, how every HTML page can be so small.
What I love about all of this is that I don’t have to write any JavaScript. Because the UI is so small, and I designed it so each element on screen doesn’t shift in layout from one page to the next, it can feel like a SPA with in-place transitions but it’s not! It requires zero JavaScript to achieve.
It feels akin to those tricks they use in film, like forced perspective, where you can achieve the illusion of something without the cost of special effects (very useful on small budgets). For example, I love this explanation of how they made the actors for Gandalf and Frodo in The Lord of the Rings appear in the same scene and, even though they’re both full-sized humans, one appeared much more hobbit-sized purely via some tricks of cameras, tracks, and props — zero computerized special effects required!
It feels a bit analogous to web development, where personal-sized projects don’t want to pull out the big “special effects” JavaScript used by big studios. You can achieve a similar, perhaps even better, effect using a few little tricks.
Reply via: Email :: Mastodon :: Bluesky
Tagged in: #omgImg
Related posts linking here: (2024) Making o(m)g:image, Part IV: URLs
2024-12-16 03:00:00
This is part two of my series of posts describing how I made my quiz game o(m)g:image.
One of my goals when making this project was to use as little JavaScript as possible.
In retrospect, I have to admit that was a pretty ambitious goal. Not because it was hard from a technical point of view, but because it was hard from the point of view of human nature.
Allow me to explain.
I write a lot of JS for my day job. And I write a lot of JS for other small projects (not necessarily websites, but scripting data, tools, etc.) And when you have the great and might hammer of JS, every solution looks like a nail.
So it required a lot of self-discipline to avoid mindlessly falling into the practice of throwing JS at every problem I wanted to solve.
For example, I could’ve made this whole site as a “single page react app”. But I’ve done that on prior personal projects and now I don’t want to ever touch any of those projects (I also hope I won’t have to). I mean, we’re talking projects with React before its major version jump, like React 0.14.x with webpack/babel/etc. (Oh young Jim, you didn’t understand as well back then.) But I’m a little smarter now, so my goal on this project was: as little JS as possible.
But JavaScript is like a smartphone: as much as you don’t want to use it, you can’t help yourself. Again, it required a constant, mindful willingness to keep stepping back and asking myself, “Can I do this without JavaScript — if possible?”
In the end, I like where I landed. Every “question” is an HTML page. Every “answer” is an HTML page. Even the “navigation” where you can see how all the questions, is an HTML page.
For example, at one point I wanted to have some navigation on each page. My first thought was something interactive on the page, like a widget that says “Question 4 of 12”, you click on it, then it expands and shows you all the questions on the quiz, including which ones you’ve answered and whether you got them right or wrong.
How do you do that without using JavaScript?
At first I thought, “Use a <details>
element, it won’t require JavaScript!” But as I thought through the ramifications, I wasn’t in love with the solution.
The more I thought about it, the more I wanted to reach for JavaScript: a fancy slide out drawer, a slick popup menu, an in-place expand. So! Many! Options!
But I wanted to avoid JS as much as possible — ”only for what’s absolutely critical” I told myself. Could I show/hide navigation with only HTML?
What I landed on was navigation as an HTML page. Every page has a little piece of information that tells you what question you’re looking at, e.g. “Question 1 of 12”.
If you click on it, rather than expanding the navigation inline, or popping out a drawer with the navigation, or doing something else that requires JavaScript to reveal the information, it simply takes you to a new HTML page with the information. (The browser’s back button serves as the equivalent of a “close drawer” or “collapse this” JavaScript solution.)
The only enhancement to the game that actually uses JS is the part that keeps track of which questions have been answered and whether the answer was correct or not. If you have JavaScript enabled, you will see this information additionally layered onto this navigational page’s UI.
With this approach, I’m truly using JavaScript for what JavaScript alone can do which is keep track of client state (you might argue I could have a server with a session here, but another constraint of the project was no server, so yeah).
This HTML-first approach really drove the design of the site. I had to ensure that navigational elements always stayed at the top of the page in consistent places across pages to ensure the UI didn’t jump as you navigated around (from the top of the page). My goal was to make the site so lean and so fast, that multi-page navigations felt as close as possible to in-place, on-page interactions.
I’m pretty happy with where the project ended up technically. I think it holds a great tension of low-cost maintenance for me over time, but still a great, solid experience for end users across a wide variety of devices — and, hopefully, across a long period of time.
The whole exercised really impressed on me once again that sometimes we just need to build simpler UIs. If your frontend is hard to build, it’s probably hard to use. If you make it easier and simpler to use, it’ll likely be easier and simpler to build and maintain.
That said, the irony here is that it’s actually quite hard to build something like this because you have to make ruthless choices about cutting things that aren’t absolutely necessary.
In other words, building websites backed by MBs of JavaScript is hard. You know what’s even harder? Questioning whether you actually need to build what you’re building, and cutting it down to the essentials. (HTML is really good at essentials.)
It reminds me of that old saying about how shorter writing requires more time, except for websites, i.e. “My website is lots of JavaScript. If I’d had more time, I would’ve made it lots of HTML.”
Reply via: Email :: Mastodon :: Bluesky
Tagged in: #omgImg
Related posts linking here: (2024) Making o(m)g:image, Part I: Design Iterations ::(2024) Making o(m)g:image, Part IV: URLs ::(2024) Making o(m)g:image, Part III: The HTML
2024-12-13 03:00:00
This is part one of my series of posts describing how I made my quiz game o(m)g:image.
I blogged about my recent project omgimg.jim-nielsen.com and I figured I’d write more details about my process behind making it.
When the idea first struck, I jumped into Figma and started working out the idea. I had a pretty good idea of what I wanted: a quiz-like website that showed one question per page.
Once you answered it gave you the result and prompted you to continue to the next question. Pretty basic stuff. In no time, I had something in Figma that fit the bill.
From there I was anxious to jump into the code, so I built the Figma mockup in code. (I built it using Web Origami, more technical details later.)
I am hosting the site on Netlify, which has atomic builds for each commit. That means I am lucky enough to go back through my deploy history and view the site at different commit iterations, so I nabbed some screenshots for this post.
The first version was pretty close to the Figma mock.
Side note: Figma, for me, is a tool for settling on the direction of a website’s design and structure, not a “implement this screenshot pixel-for-pixel” tool, so I knew things were going to change and evolve as I built the site in the browser. Building the site is part of the design process.
That worked, but I didn’t love it. The relationships between elements didn’t quite feel right, so I started playing with layout and other small things.
From there, I started clicking around and going through each question. This gave me a feel for the experience of answering questions, seeing the result, and moving on to the next question.
I had figured out (in my mind’s eye) how this would work in Figma, but only once I had a working prototyping in a web browser could I really get a feel for how the Figma design held up. This gave me a chance to say to myself, “Oh well that doesn’t work, and I’ll have to change this, and this thing over here...”
That's how I ended up on another iteration where I tweaked element positioning to try and better the experience of: seeing a question, answering it, then viewing the result.
After I got the navigational elements to a place that felt better (and indicating your progress through the quiz), I went back to layout and played with the overall positioning of elements. I also continued to refine and tweak the elements that helped you navigate through the questions of the game (and their corresponding answer pages).
At this point I ended up taking a detour on the navigation front.
I kept waffling between the idea of minimal representation of the number of questions in the game (e.g. “1/10 questions”) and something more robust that would let you jump to any question at any point. So I built a version to test out that idea.
Then I ended up on a side quest of multi-page view transitions, because it was cool to be able to design the navigation in such a way that I could have multiple HTML pages that transitioned visually when navigating between them.
But I ended up deciding I didn’t want to go in that direction, so I went back to a minimal representation of question status/progress. I also changed the layout (yet again). These were all decisions I made after repeated usage of the game, just clicking through things over and over and over until I found an experience that felt seamless and fast.
I also decided that, rather than have all questions laid out in the navigation for every single question, I would have a single page with all the questions represented on it. As an enhancement, people with JavaScript would see their progress on that page including the correctness of their answer for each question.
That felt good to me, so I shipped it and that’s the state of the site at the time of this writing.
It’ll probably change in the future because every time I use it I think, “Oh I could tweak this, and move that...”
That’s the curse of building these things yourself.
Reply via: Email :: Mastodon :: Bluesky
Tagged in: #omgImg
Related posts linking here: (2024) Making o(m)g:image, Part IV: URLs ::(2024) Making o(m)g:image, Part II: As Little JS As Possible ::(2024) Making o(m)g:image, Part III: The HTML
2024-12-10 03:00:00
I was popping off on Mastodon with an idea for a physical board game then decided to just make a digital version. It’s called “o(m)g:image” and you can play it now:
Here’s the idea:
og:image
pulled from an online articleFor example, can you guess the title of the article for this image?
Is it:
Answer: it’s an article from slack.com
and its real title is number 4 above.
Did you get it right?
Without question, we seem to have accepted (and stretch ourselves to support) this idea that every web page in the entire world should should be annotated with an image that encapsulates its contents into a single, visual expression.
This game is meant to illustrate the absurdity of this notion in both principle and real-world execution.
I often vent about social share imagery to anyone who will listen — both in person and on my blog.
Sometimes a satirical game can convey the idea better than words.
Nothing inherently.
But because they’re possible, everyone feels like they’re leaving clicks and pageviews on the table if they don’t implement them.
“You’re not going to have an image for every post? Are you a crazy person?! People are 1,267% more likely to click through to your website if you have one!”
So everyone makes them, and as a result they end up stretching themselves — and the meaning between the image the content — to come up with these images.
That article has a picture of a clock? It could be about anything, such as:
It could also have nothing to do with clocks and just be an article for a technical blog where nobody could come up with a better idea of how to visually express “Latest news from our release of [insert codebase name here]”.
For anybody who has done it, the task of coming up with a good image for every article is a tough one. The web has turned everyone into a publisher, but not everyone is backed by a large media corporation that employs people to do this task as their full-time job. If you as an individual or a small group of people try to compete with the quality of social share imagery from media publications, it’ll never be as good. Full stop. In other words: you’ll never be as good at the game as the companies who do it for a living, so the only way to win is not to play the game.
Or, you can try to compete and then you reach for automation. As Nicholas Carr says, “The endless labor of self-expression cries out for the efficiency of automation.” But automated social share images have their downfalls:
og:title
and og:description
and, on many social sites, displayed under the image itself)Social share images are basically billboards on the information superhighway. Perhaps they were intended to convey additional context about the article, but in practice they’re really just screaming to catch your eye (and attention) and hopefully make you click.
Perhaps even better than a contextual image summarizing the content of the article, these images should just have some flamboyant visual with giant, bold type set in Impact that says, “BET YOU COULD NEVER GUESS WHAT IS HERE! CLICK TO FIND OUT!!!"
Look I get it. Social platforms are giving you the chance to have a free billboard, one that might grab the attention of all the people passing it by in their feeds. Why wouldn’t you leverage a free billboard?
I mean this game, which makes fun of social share images, even has one. So who the hell am I to say anything?
Nobody really, just another person on the internet.
Again, you can check out the game at: omgimg.jim-nielsen.com
Reply via: Email :: Mastodon :: Bluesky
Tagged in: #omgImg
Related posts linking here: (2024) Making o(m)g:image, Part I: Design Iterations ::(2024) Making o(m)g:image, Part IV: URLs ::(2024) Making o(m)g:image, Part II: As Little JS As Possible ::(2024) Making o(m)g:image, Part III: The HTML
2024-12-03 03:00:00
Which is best?
This list could go on forever. Zoom in to just the JavaScript ecosystem and its overwhelming:
Even here, the list could go on forever: db libraries, task runners, testing libraries, UI metaframeworks, server frameworks, state management libraries, etc.
Module systems — ESM, CJS, AMD, UMD — are a great example: how could we truly understand any of them individually without having had all of them?
We need opposing options. They exist not solely in opposition to one another but as contrast.
We must have a diversity to understand and discuss which is most fit for a given context. The web is big! “It depends”!
How do you understand one thing without the contrast of its opposite? What is white without black? How do you understand salty without sweet? One, by definition, excludes the other, which gives form and shape to the definition of each.
And guess what? Whichever you choose, you’ll likely choose poorly. That’s ok. Choosing poorly is where growth happens — if you let it.
Silver bullets are for killing werewolves not building technology.