-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Improve bat theme * Simplify lock * Namespace CSS variables * Dont show bats on print --------- Co-authored-by: Kresten Laust <[email protected]>
- Loading branch information
1 parent
fccc11d
commit 124ba7f
Showing
10 changed files
with
300 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,75 @@ | ||
.bat-container { | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
bottom: 0; | ||
pointer-events: none; | ||
@property --bat-x { | ||
syntax: "<number>"; | ||
inherits: true; | ||
initial-value: 0; | ||
} | ||
@property --bat-y { | ||
syntax: "<number>"; | ||
inherits: true; | ||
initial-value: 0; | ||
} | ||
@property --bat-direction { | ||
syntax: "<number>"; | ||
inherits: true; | ||
initial-value: 0; | ||
} | ||
@property --bat-delay { | ||
syntax: "<number>"; | ||
inherits: true; | ||
initial-value: 0; | ||
} | ||
|
||
#bat-container { | ||
position: fixed; | ||
inset: 1lvw 1lvh; | ||
margin: 0; | ||
transition: opacity 2s ease; | ||
pointer-events: none; | ||
|
||
&.stationary { | ||
opacity: 0.7; | ||
|
||
.bat { | ||
position: fixed; | ||
left: 0; | ||
top: 0; | ||
opacity: 0; | ||
transition-property: top, left, opacity; | ||
transition-delay: 0s; | ||
transition-timing-function: linear; | ||
@media (prefers-contrast) or (prefers-reduced-transparency) { | ||
display: none; | ||
} | ||
} | ||
|
||
@media not screen { | ||
display: none; | ||
} | ||
|
||
& .bat { | ||
position: absolute; | ||
width: 10%; | ||
height: 5%; | ||
left: 0; | ||
top: 0; | ||
scale: var(--bat-direction) 1; | ||
translate: calc(9% * var(--bat-x)) calc(19% * var(--bat-y)); | ||
background: url(./sprites/0.png) no-repeat center / contain; | ||
animation: 600ms steps(1, end) calc(-6ms * var(--bat-delay)) infinite bat-flap; | ||
image-rendering: pixelated; | ||
|
||
@media (prefers-reduced-motion) { | ||
animation-play-state: paused !important; | ||
} | ||
} | ||
} | ||
|
||
.bat img { | ||
image-rendering: pixelated; | ||
} | ||
@keyframes bat-flap { | ||
0% { | ||
background-image: url(sprites/0.png); | ||
} | ||
33.33% { | ||
background-image: url(sprites/1.png); | ||
} | ||
50% { | ||
background-image: url(sprites/2.png); | ||
} | ||
66.67% { | ||
background-image: url(sprites/3.png); | ||
} | ||
83.33% { | ||
background-image: url(sprites/4.png); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,191 @@ | ||
/*! JS Bat 2013 - v1.2 - Eric Grange - www.delphitools.info */ | ||
|
||
function spawn_bat() { | ||
var v = document.createElement('video'); | ||
var s = document.createElement('source'); | ||
var z = document.createElement('div'); | ||
var zs = z.style; | ||
var a = window.innerWidth * Math.random(); | ||
var b = window.innerHeight * Math.random(); | ||
z.classList.add("bat"); | ||
z.appendChild(v); | ||
v.appendChild(s); | ||
v.width = "48"; | ||
v.height = "48"; | ||
v.autoplay = true; | ||
v.loop = true; | ||
v.muted = true; | ||
s.src = themes_static_url + "bat/bat.webm"; | ||
s.type = "video/webm"; | ||
document.body.querySelector(".bat-container").appendChild(z); | ||
|
||
function R(o, m) { | ||
return Math.max(Math.min(o + (Math.random() - 0.5) * 400, m - 50), 50); | ||
} | ||
|
||
function A(){ | ||
var x = R(a, window.innerWidth); | ||
var y = R(b, window.innerHeight); | ||
var d = Math.round(10 * Math.sqrt((a - x) * (a - x) + (b - y) * (b - y))); | ||
zs.opacity = 1; | ||
zs.transitionDuration = zs.webkitTransitionDuration = d + 'ms'; | ||
// zs.transform = zs.webkitTransform = 'translate(' + x + 'px, ' + y + 'px)'; | ||
zs.left = x + 'px'; | ||
zs.top = y + 'px'; | ||
v.style.transform = v.style.webkitTransform = (a > x) ? '' : 'scaleX(-1)'; | ||
a = x; | ||
b = y; | ||
setTimeout(A,d); | ||
} | ||
setTimeout(A, Math.random() * 3e3); | ||
}; | ||
|
||
const d = new Date(); | ||
|
||
for(let n_bats=0; n_bats < d.getDate(); n_bats++){ | ||
spawn_bat(); | ||
// Fallback for WebKit: https://caniuse.com/requestidlecallback | ||
// TODO: remove this crap as soon as WebKit supports the real deal | ||
const requestIdleCallback = | ||
globalThis.requestIdleCallback ?? | ||
((func, { timeout }) => { | ||
const maxWait = Math.min(timeout ?? Infinity, 100); | ||
return setTimeout(func, maxWait); | ||
}); | ||
const cancelIdleCallback = globalThis.cancelIdleCallback ?? clearTimeout; | ||
|
||
// The minimum amount of time that CSS animations should be buffered for | ||
const minAnimationBuffer = 5_000; | ||
// The maximum amount of time that CSS animations should be buffered for | ||
const maxAnimationBuffer = 10_000; | ||
// Make this bigger to make bats go faster | ||
const speedMultiplier = 150; | ||
|
||
// The HTML container that our bats exist in | ||
const container = document.querySelector("#bat-container"); | ||
|
||
// The ID of the timeout that is currently waiting to call `pointAndShoot` | ||
let timeoutId; | ||
// A queue of all the bats, ordered by when they will need new coordinates | ||
const batQueue = []; | ||
|
||
// Initial setup of the bats queue | ||
for (const element of container.querySelectorAll(".bat")) { | ||
batQueue.push({ | ||
element, | ||
nextFly: 0, | ||
}); | ||
} | ||
|
||
// Ensure that we disable this stuff if the user prefers reduced motion, | ||
// or if the user is not looking at the page | ||
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion)"); | ||
prefersReducedMotion.addEventListener("change", handleStationaryChange); | ||
document.addEventListener("visibilitychange", handleStationaryChange); | ||
handleStationaryChange(); | ||
function handleStationaryChange() { | ||
if (prefersReducedMotion.matches || document.visibilityState === "hidden") { | ||
pauseShooting(); | ||
} else { | ||
resumeShooting(); | ||
} | ||
} | ||
/** | ||
* Makes sure everything stops running and removes any animations. | ||
*/ | ||
function pauseShooting() { | ||
// Let CSS know to stop moving | ||
container.classList.add("stationary"); | ||
// Stop shooting bats around | ||
clearTimeout(timeoutId); | ||
cancelIdleCallback(timeoutId); | ||
timeoutId = undefined; | ||
} | ||
/** | ||
* Kickstarts everything again. | ||
*/ | ||
function resumeShooting() { | ||
// Let CSS know to start moving again | ||
container.classList.remove("stationary"); | ||
// Start shooting bats around again | ||
prepareNextShot(); | ||
} | ||
|
||
/** | ||
* Call this function to ensure that bats are getting new coordinates. | ||
* DO NOT call the `pointAndShoot` function directly, as that might | ||
* cause two functions to be running simultaneously. | ||
*/ | ||
function prepareNextShot() { | ||
if (timeoutId !== undefined) { | ||
return; | ||
} | ||
const bat = batQueue[0]; | ||
const timeToNextFly = Math.max(0, bat.nextFly - Date.now()); | ||
if (timeToNextFly < 10) { | ||
// If the bat needs new coordinates RIGHT NOW, schedule it quick | ||
// with a very short timeout. | ||
// We do not run it directly, as running this back-to-back 30 times | ||
// in a row would block user input and make the page feel janky. | ||
timeoutId = setTimeout(pointAndShoot, 0); | ||
} else if (timeToNextFly < maxAnimationBuffer) { | ||
// If the bat needs new coordinates sometime before the max buffer time, | ||
// schedule an idle callback, so it doesn't interfere with more important tasks. | ||
timeoutId = requestIdleCallback(pointAndShoot, { timeout: timeToNextFly }); | ||
} else { | ||
// If the bat needs new coordinates later than our max buffer time, | ||
// take a chill pill and schedule a timeout that wakes us up when | ||
// we get close to our min buffer time limit. | ||
timeoutId = setTimeout(() => { | ||
timeoutId = undefined; | ||
prepareNextShot(); | ||
}, timeToNextFly - minAnimationBuffer); | ||
} | ||
} | ||
/** | ||
* Gives the next bat in the queue some new coordinates. | ||
* DO NOT call this directly, call `prepareNextShot` instead. | ||
*/ | ||
function pointAndShoot() { | ||
// Make it clear that a new timeout can be scheduled | ||
timeoutId = undefined; | ||
|
||
// Get the next bat in the queue. | ||
const bat = batQueue.shift(); | ||
|
||
// On first load, we need to get the bat position from the HTML | ||
bat.x ??= Number(bat.element.style.getPropertyValue("--bat-x")); | ||
bat.y ??= Number(bat.element.style.getPropertyValue("--bat-y")); | ||
|
||
// Calculate new coordinates | ||
const { coordinate: newX, direction } = newCoordinate(bat.x); | ||
const { coordinate: newY } = newCoordinate(bat.y); | ||
// Calculate the animation time based on how far | ||
// the new coordinates are from the previous | ||
const distance = Math.sqrt((bat.x - newX) ** 2 + (bat.y - newY) ** 2); | ||
const flyTime = speedMultiplier * distance; | ||
|
||
const now = Date.now(); | ||
// If we are late to the party, we pretend that the bat was supposed to fly right now | ||
bat.nextFly = Math.max(bat.nextFly, now); | ||
|
||
// Set the animation in the DOM | ||
const batDirection = direction * -1; | ||
bat.element.animate( | ||
[ | ||
{ "--bat-x": bat.x, "--bat-y": bat.y, "--bat-direction": batDirection }, | ||
{ "--bat-x": newX, "--bat-y": newY, "--bat-direction": batDirection }, | ||
], | ||
{ | ||
delay: bat.nextFly - now, | ||
duration: flyTime, | ||
fill: "forwards", | ||
}, | ||
); | ||
|
||
// Set everything in our local bat object so we know what's up next time | ||
bat.nextFly += flyTime; | ||
bat.x = newX; | ||
bat.y = newY; | ||
|
||
// Put it back in the bats array. | ||
// Bats must be ordered such that the first element is always the next one that needs to be shot. | ||
let i; | ||
for (i = 0; i < batQueue.length; i++) { | ||
if (batQueue[i].nextFly > bat.nextFly) { | ||
break; | ||
} | ||
} | ||
batQueue.splice(i, 0, bat); | ||
|
||
// Move on to the next bat in need | ||
prepareNextShot(); | ||
} | ||
|
||
/** | ||
* Generates a new coordinate that is at least 2% different from | ||
* the previous, and at most 15% different. | ||
* | ||
* @param {number} previous - The previous coordinate. | ||
*/ | ||
function newCoordinate(previous) { | ||
// The minimum amount it can change in percentage | ||
const min = 2; | ||
// The amount it can change beyond the minimum, in percentage | ||
const maxRange = 13; | ||
|
||
const change = min + maxRange * Math.random(); | ||
let direction = Math.random() < 0.5 ? 1 : -1; | ||
let coordinate = previous + change * direction; | ||
// Make sure that the bat doesn't move outside the page | ||
if (coordinate < 0 || 100 < coordinate) { | ||
direction *= -1; | ||
coordinate = previous + change * direction; | ||
} | ||
return { coordinate, direction }; | ||
} | ||
|
||
/** | ||
* Ensure that the value is somewhere between a min and max value. | ||
* | ||
* @param {number} min - The smallest permitted value. | ||
* @param {number} value - The value to clamp. | ||
* @param {number} max - The largest permitted value. | ||
*/ | ||
function clamp(min, value, max) { | ||
return Math.max(min, Math.min(max, value)); | ||
} |
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,17 @@ | ||
<div class="bat-container"></div> | ||
{% load themes %} | ||
{% load stregsystem_extras %} | ||
{% day_of_month as day %} | ||
|
||
<div id="bat-container" class="stationary"> | ||
{% for i in day|to_range %} | ||
<div | ||
class="bat" | ||
style=" | ||
--bat-x: {% random 0 100 %}; | ||
--bat-y: {% random 0 100 %}; | ||
--bat-direction: {% random_choice 1 -1 %}; | ||
--bat-delay: {% random 0 100 %}; | ||
" | ||
></div> | ||
{% endfor %} | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters