Kentucky Derby Magic 8 Ball

Launched

2018

Filed Under

A list of tags for this post.

Role

  • Code
  • Design

Platform

  • Glitch

I started this project in 2018 based on a tutorial by Kelly Lougheed by remixing her Glitch Project (thank you!). At the time my Javascript skills were both minimal and very rusty, and this presented a good opportunity have some fun while learning something. Each year since I’ve been able to make improvements.

2018 version

(permalink)

The biggest accomplishment of the inaugural version, aside from completing it, was modifying it to exclude having to ask the question, albeit what I came up with wasn’t very elegant, to put it politely. In the spirit of “you have to start somewhere”, this was a decent start.

2019 version

(permalink)

In 2019 I made some refinements to the Javascript, which felt like an accomplishment as I had a better understanding of what was happening rather than just poking at it with no real clue like I had done in the first version. I also made a few CSS and design refinements but the JavaScript was the biggest improvement.

The original version used if/else statements and innerHTML to swap out the placeholder “8” and display the randomized answer.

document.getElementById('answerButton').onclick = function () {var x = document.getElementById("eight");
if (x.innerHTML === "8") {
x.innerHTML = "";
} else {
x.innerHTML = "";
}
var answer = answers[Math.floor(Math.random() * answers.length)];
document.getElementById('answerContainer').innerHTML = answer;
};

I changed it use getElementById to manipulate the style of the container (applying the style directly in JavaScript) and then writing the answer using textContent.

document.getElementById('answerButton').onclick = function () {

let resizeAnswer = document.getElementById("answerContainer").style.fontSize = '2rem';
let derbyWinner = contenders[Math.floor(Math.random() * contenders.length)];

document.getElementById('answerContainer').textContent = derbyWinner;
};

I’m guessing the updated approach is better than the previous. If nothing else I found the new approach easier to write and understand.

2020 version

(permalink)

This version took a few steps forward with more JavaScript refinements as well as some CSS and accessibility improvements. I simplified the JavaScript even further by using getElementById with classList.remove and classList.add to swap the “8” and the answer, moving the styling to CSS.

document.getElementById('answer-button').onclick = function () {

document.getElementById('answer-container').classList.remove('eight');
document.getElementById('answer-container').classList.add('reveal');
let derbyWinner = contenders[Math.floor(Math.random() * contenders.length)];

document.getElementById('answer-container').textContent = derbyWinner;
};

The CSS improvements were mostly general clean-up, like moving from fixed sizes to relative. I also included a couple of nice enhancements found in articles. I used this by Håvard Brynjulfsen to spiff up the focus styles on the button and add a nice drop shadow and the squishy button active state came from a Piccalilli Quick Tip.

For accessibility I tested using Mac OS VoiceOver and it announced properly in Safari and Chrome, but not Firefox.

2021 version

(permalink)

This version added some 8 ball-esque animation, improved accessibility and updated design. I might have gone a little overboard with the existing JavaScript approach, but I used the existing getElementById DOM manipulation approach to add animation styles and change the aria-hidden attribute.

document.getElementById('answer-button').onclick = function () {

document.getElementById('answer-container').classList.remove('eight');
document.getElementById('answer-container').classList.add('reveal');
document.getElementById('answer-container').setAttribute("aria-hidden", false); // remove aria-hidden attribute so the answer is read
document.getElementById('answer').classList.add('answer');
document.getElementById('eight-ball').classList.add('shake');
let derbyWinner = contenders[Math.floor(Math.random() * contenders.length)];

document.getElementById('answer-container').textContent = derbyWinner;
};

The first two lines within the function are the existing swap of the placeholder “8” and styling for the randomized answer. The container is set to aria-hidden=“true” by default to keep screen readers from announcing the placeholder “8”. When the answer is added aria-hidden is changed “false”, allowing the answer to announced. This worked in VoiceOver, and I’m hoping it works in other screen readers. The next two additions apply the animations.

I also made a bit of an effort to make it look more like an 8 ball. I wrote a detailed account of the 2021 changes, including more about the accessibility of the animations and some details about the design if you’re interested.

2022 version

(permalink)

This version moved from Glitch to Netlify, mostly to improve my workflow. I also added a dark theme and dropped the media queries.

The media queries were mainly used to control the size of the eight ball. I changed that to use clamp() with a fallback, which gives the eight ball a better size and screen usage across devices.

Original sizing

.eight-ball {
background-color: black;
border-radius: 50%;
width: 22rem;
height: 22rem;
font-size: 2.2rem;
}

@media only screen and (max-width: 800px) {
.eight-ball {
width: 18rem;
height: 18rem;
}
}

Updated sizing

.eight-ball {
background-color: var(--color-darkest);
border-radius: 50%;
border: 1px solid var(--color-grey-mid);
width: 18rem;
width: clamp(18rem, 30vw, 36rem);
height: 18rem;
height: clamp(18rem, 30vw, 36rem);
font-size: 2.2rem;
}

I updated the answer background to a blue gradient to give it a bit more of an original 8 ball vibe without trying to figure out how to put the potentially long answers in a triangle.

I’d like to think I could improve the animation, but overall I’m happy with the current state.

View all versions…