Updates with dvd logo animation; add cool predictions; update index page with description
This commit is contained in:
parent
59ceec2b60
commit
230f98aa49
@ -6,20 +6,42 @@
|
|||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: black;
|
background-color: black;
|
||||||
height: 100%;
|
}
|
||||||
width: 100%;
|
#container {
|
||||||
|
margin: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
#dvd-logo {
|
#dvd-logo {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -200px; /* So that logo doesn't flash at top left corner when document loads */
|
left: -200px;
|
||||||
top: -200px;
|
top: -200px;
|
||||||
}
|
}
|
||||||
|
.controls {
|
||||||
|
padding: 12px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(255,255,255,0.5);
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-right: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="container">
|
||||||
<img id="dvd-logo" src="images/dvd-logo.png">
|
<img id="dvd-logo" src="images/dvd-logo.png">
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<span id="iteration-count"></span>
|
||||||
|
<br>
|
||||||
|
<input type="range" min="0" max="500" id="iteration-slider">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script src="js/script.js"></script>
|
<script src="js/script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,42 +1,135 @@
|
|||||||
|
const global_state = {
|
||||||
|
iterations: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALLOWED_ERROR = 0.01;
|
||||||
|
|
||||||
const randUpTo = (max) => Math.random() * max
|
const randUpTo = (max) => Math.random() * max
|
||||||
|
|
||||||
const randomHue = (element) => {
|
const withinError = (a, b, error=ALLOWED_ERROR) => Math.abs(a - b) < error;
|
||||||
element.style.filter = `brightness(0.5) sepia(1) saturate(10000%) hue-rotate(${randUpTo(360)}deg)`;
|
|
||||||
|
const normalize = (vec) => {
|
||||||
|
const magnitude = Math.sqrt(vec.x * vec.x + vec.y * vec.y);
|
||||||
|
return {
|
||||||
|
x: vec.x / magnitude,
|
||||||
|
y: vec.y / magnitude
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const clamp = (val, max) => val > max ? max : val;
|
const findNextIntersection = ({x, y, dx, dy}, max_width, max_height) => {
|
||||||
|
const slope = dy / dx;
|
||||||
|
const y_intercept = y - (slope * x);
|
||||||
|
const intersections = [
|
||||||
|
{x: 0, y: y_intercept},
|
||||||
|
{x: max_width, y: slope * max_width + y_intercept},
|
||||||
|
{x: -y_intercept / slope, y: 0},
|
||||||
|
{x: (max_height - y_intercept) / slope, y: max_height}
|
||||||
|
];
|
||||||
|
return intersections.find((cross) => {
|
||||||
|
const cross_dir_hat = normalize({x: cross.x - x, y: cross.y - y});
|
||||||
|
const logo_dir_hat = normalize({x: dx, y: dy});
|
||||||
|
const in_bounds = cross.x >= 0 && cross.x <= max_width && cross.y >= 0 && cross.y <= max_height;
|
||||||
|
return in_bounds && withinError(cross_dir_hat.x, logo_dir_hat.x) && withinError(cross_dir_hat.y, logo_dir_hat.y);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCollision = (obj, max_width, max_height) => {
|
||||||
|
const collide_y = obj.y <= 0 || obj.y >= max_height;
|
||||||
|
const collide_x = obj.x <= 0 || obj.x >= max_width;
|
||||||
|
|
||||||
const updateLogo = (logo, element) => {
|
obj.dx *= (collide_x ? -1 : 1);
|
||||||
const screen_height = window.innerHeight;
|
obj.dy *= (collide_y ? -1 : 1);
|
||||||
const screen_width = window.innerWidth;
|
|
||||||
|
|
||||||
const collide_y = logo.y <= 0 || (logo.y + element.clientHeight) >= screen_height;
|
return {collide_y, collide_x};
|
||||||
const collide_x = logo.x <= 0 || (logo.x + element.clientWidth) >= screen_width;
|
}
|
||||||
|
|
||||||
logo.dx *= (collide_x ? -1 : 1);
|
|
||||||
logo.dy *= (collide_y ? -1 : 1);
|
|
||||||
|
|
||||||
|
const updateLogo = (logo, max_width, max_height) => {
|
||||||
|
const {collide_x, collide_y} = handleCollision(logo, max_width, max_height);
|
||||||
if (collide_y || collide_x) {
|
if (collide_y || collide_x) {
|
||||||
randomHue(element);
|
logo.dColorWheel = randUpTo(2*Math.PI)
|
||||||
|
logo.onIntersection(logo, max_width, max_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp in case user changes screen size
|
logo.x = Math.max(0, Math.min(logo.x + logo.dx, max_width));
|
||||||
logo.x = clamp(logo.x + logo.dx, screen_width - element.clientWidth);
|
logo.y = Math.max(0, Math.min(logo.y + logo.dy, max_height));
|
||||||
logo.y = clamp(logo.y + logo.dy, screen_height - element.clientHeight);
|
};
|
||||||
|
|
||||||
|
const drawLogo = (logo, element) => {
|
||||||
element.style.left = `${logo.x}px`;
|
element.style.left = `${logo.x}px`;
|
||||||
element.style.top = `${logo.y}px`;
|
element.style.top = `${logo.y}px`;
|
||||||
};
|
element.style.filter = `brightness(0.5) sepia(1) saturate(10000%) hue-rotate(${logo.dColorWheel}rad)`;
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
let logo = {
|
|
||||||
x: randUpTo(window.innerWidth),
|
|
||||||
y: randUpTo(window.innerHeight),
|
|
||||||
dx: 2,
|
|
||||||
dy: 2
|
|
||||||
};
|
|
||||||
const dvdLogo = document.getElementById("dvd-logo");
|
|
||||||
|
|
||||||
setInterval(() => updateLogo(logo, dvdLogo), 15); // ~ 67 hz
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const makeSvg = (coords) => {
|
||||||
|
const makeLine = (a, b, color='white') => `<line x1="${a.x}" x2="${b.x}" y1="${a.y}" y2="${b.y}" stroke="${color}" />`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<svg xmlns='http://www.w3.org/2000/svg' width='${window.innerWidth}px' height='${window.innerHeight}px'>
|
||||||
|
<rect x="0" y="0" width="${window.innerWidth}" height="${window.innerHeight}" fill="black"/>
|
||||||
|
${coords.map((_,i) => {
|
||||||
|
if (i == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return makeLine(coords[i-1], coords[i], 'red');
|
||||||
|
}).join('')}
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setBackgroundToSvg = (svg) => {
|
||||||
|
document.getElementById("container").style.background = 'url(data:image/svg+xml;base64,'+btoa(svg)+')';
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeListOfFutureCollisionsAndDraw = (logo, max_width, max_height) => {
|
||||||
|
let next_int = {x: logo.x, y: logo.y, dx: logo.dx, dy: logo.dy};
|
||||||
|
let intersections = [{...next_int}];
|
||||||
|
|
||||||
|
for (let i = 0; i < global_state.iterations; i++) {
|
||||||
|
const next = findNextIntersection(next_int, max_width, max_height);
|
||||||
|
next_int.x = next.x;
|
||||||
|
next_int.y = next.y;
|
||||||
|
handleCollision(next_int, max_width, max_height);
|
||||||
|
intersections.push(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform each intersection to the center of the logo
|
||||||
|
intersections.map((x) => {
|
||||||
|
x.x += logo.width/2;
|
||||||
|
x.y += logo.height/2;
|
||||||
|
});
|
||||||
|
|
||||||
|
setBackgroundToSvg(makeSvg(intersections));
|
||||||
|
};
|
||||||
|
|
||||||
|
let drawNewPaths;
|
||||||
|
window.onload = () => {
|
||||||
|
const dvdLogo = document.getElementById("dvd-logo");
|
||||||
|
let logo = {
|
||||||
|
x: Math.floor(randUpTo(window.innerWidth-dvdLogo.clientWidth)),
|
||||||
|
y: Math.floor(randUpTo(window.innerHeight-dvdLogo.clientHeight)),
|
||||||
|
dx: 2,
|
||||||
|
dy: 2,
|
||||||
|
width: dvdLogo.clientWidth,
|
||||||
|
height: dvdLogo.clientHeight,
|
||||||
|
onIntersection: makeListOfFutureCollisionsAndDraw,
|
||||||
|
dColorWheel: 0
|
||||||
|
};
|
||||||
|
setInterval(() => {
|
||||||
|
updateLogo(logo, window.innerWidth-logo.width, window.innerHeight-logo.height);
|
||||||
|
drawLogo(logo, dvdLogo);
|
||||||
|
}, 22);
|
||||||
|
|
||||||
|
drawNewPaths = () => makeListOfFutureCollisionsAndDraw(logo, window.innerWidth-logo.width, window.innerHeight-logo.height);
|
||||||
|
drawNewPaths();
|
||||||
|
window.onresize = drawNewPaths;
|
||||||
|
|
||||||
|
document.getElementById("iteration-slider").value = global_state.iterations;
|
||||||
|
document.getElementById("iteration-count").innerHTML = `${global_state.iterations} iterations`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("iteration-slider").oninput = function() {
|
||||||
|
global_state.iterations = parseInt(this.value, 10);
|
||||||
|
document.getElementById("iteration-count").innerHTML = `${global_state.iterations} iterations`;
|
||||||
|
drawNewPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@
|
|||||||
<br>
|
<br>
|
||||||
🤓 I'm Simponic.
|
🤓 I'm Simponic.
|
||||||
<br>
|
<br>
|
||||||
📖 This pages hosts strictly static content.
|
📖 This page hosts strictly static content.
|
||||||
<br>
|
<br>
|
||||||
🔔 Visit <a href="https://simponic.xyz">simponic.xyz</a> to view my "real" website.
|
🔔 My "real website" is at <a href="https://simponic.xyz">simponic.xyz</a>.
|
||||||
</p>
|
</p>
|
||||||
<div class="projects-grid">
|
<div class="projects-grid">
|
||||||
<div class="project" onclick="window.location='dvd-logo/index.html'">
|
<div class="project" onclick="window.location='dvd-logo/index.html'">
|
||||||
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
<div class="project-body">
|
<div class="project-body">
|
||||||
<h1>DVD Logo Bouncing Animation</h1>
|
<h1>DVD Logo Bouncing Animation</h1>
|
||||||
<p>Brings back the nostalgia of old-school DVD players, as discussed in <a href="https://www.youtube.com/watch?v=QOtuX0jL85Y">The Office</a>.</p>
|
<p>Brings back the nostalgia of old-school DVD players with an intersection predictor. The twist: no Canvas API! Only svg's and absolute positioned images!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user