writing about this website.
Table of Contents
April 10th, 2017
writing on April 17th, 2017
Loading Effect
When you first hit the webpage, the intended effect is the webside creating itself. What is actually happening is styles are being loaded onto the page dynamically. The page starts out with only the following styles, which hide most of the elements on the page.
.projects.projects, .gallery, .navigation, .links, .name, .label, img, svg, li { display: none }
Upon loading the page, this javascript is executed. It loads styles.css
into memory, then creates
a style
element and pre
element, which is used to display code. It puts both these elements on
the page, then adds text from styles.css
to both of them line by line. This has the effect of
changing the styles on the page at the same time that they show up on screen.
fetch("/styles.css").then(r => r.text()).then(text => {
const lines = text.split('\n');
const len = lines.length;
const style = document.createElement('style');
document.head.appendChild(style);
const pre = document.createElement('pre');
document.getElementsByClassName('container')[0].appendChild(pre);
const write = (ls) => {
if (ls.length === 0) {
document.getElementById('pre').remove()
} else {
const l = ls.shift() + "\n";
style.textContent += l;
pre.textContent += l;
We use this timing function to add lines faster and faster to the screen. Eventually, there’s a
style rule that hides the pre
element. However, we only do all this on the first time the user
hits the page. Immediately after hitting the page, you might notice that they URL changes to
‘/home’. This will be explained later…
setTimeout(() => write(ls), 250/Math.floor((len - ls.length)/3));
}
};
(window.location.pathname === "/" ? () => write(lines) : () => {style.textContent = lines.join("\n"); write([]);})();
nav(window.location.pathname.substring(1) || "home");
});
Profile Image
The profile image is animated using an svg polygon
element and the stroke-dasharray
and
stroke-dashoffset
properties. reference.
Navigation
The three pages on the page are just different div
s called home, gallery, projects. Navigation
between them is just done by setting styles for them to be shown or not. There are symlinks for
home.html
, gallery.html
, and proejcts.html
that point to index.html
, so if you revisit the
page or pass around the URL, this should still work.
const nav = (page) => {
window.history.pushState({}, page, page);
['home', 'gallery', 'projects'].map(c =>
document.getElementsByClassName(c)[0].className = c + (c === page ? " active" : " hidden")
);
}
Projects
Projects are just loaded in dynamiaclly from a file called projects.json
.
Note, the following code on this page is pretty ugly.
fetch("/projects.json").then(r => r.json()).then(data => {
const projects = document.getElementsByTagName('ol')[0];
data.map(p => {
const el = document.createElement('li');
el.innerHTML = '<a href="'+p.link+'" '+(p.link[0] === '/' ? '' : 'target="_blank"')+'><div class="title">'+p.title+'</div><p class="desc">'+p.desc+'</p><div class="pos">'+p.alt+'</div></a>';
projects.appendChild(el);
});
});
Gallery
The gallery is implemented in a similar way to projects, execept the structure is little more
involved. Like before, we load a file gallery.json
which contains filenames for all the photos and
their description, split up by section. We create labels for each section, which are just
horizontally scrolling divs.
fetch("/gallery.json").then(r => r.json()).then(data => {
const albums = document.getElementsByClassName('albums')[0];
Object.keys(data).map((k,i) => {
const wrap = document.createElement('div');
wrap.innerHTML = '<div class="label">'+k+'</div>';
I can’t seem to find the page where I found this trick, but you can use radio style inputs to create
a css-only gallery. CSS allows you to select adjacent siblings, so we put a radio input
before
each image. When that radio button is checked, we can show the li
elem that directly follows it.
Then, we set onclick handlers on each image to check their corresponding radio input. Each image also has previous and next buttons, which will check the radio buttons for the previous and next images, respectively.
There’s this annoying CSS quick that I’ve ran into oh so many times in the past. You
can’t have overflow
scrollable on one axis and visible on the other (I believe). This manifests
here because when we want to show the full image, we can’t escape the bounds of the smaller
horizontal album. The solution–you can explore styles.css
–is to just move everything else out of
the way and make the selected album take up the entire screen. This state is marked by the class
fullscreen
. Annoying, but works.
The span
is placed around the img
to allow us to place the description for the iamge below each
image. The description is normally hidden, and is only shown in the fullscreen
state.
const list = document.createElement('ul');
data[k].map((f,j) => {
const btn = document.createElement('div');
btn.innerHTML = '<input type="radio" name="fullscreen" id="'+(i+'.'+j)+'"/>';
const item = document.createElement('li');
item.innerHTML = ('<span><img src="'+("/gallery/"+k+"/"+f.file)+'"/><span>'+f.desc+'</span></span>'+
(j > 0 ? '<label class="prev" for="'+(i+'.'+(j-1))+'">‹</label>' : '')+
(j < data[k].length - 1 ? '<label class="next" for="'+(i+'.'+(j+1))+'">›</label>' : ''));
const input = btn.firstChild;
input.onchange = () => albums.className = "albums fullscreen";
item.firstChild.onclick = () => input.click();
list.appendChild(input);
list.appendChild(item);
})
wrap.appendChild(list);
albums.appendChild(wrap);
});
document.getElementsByClassName('close')[0].onclick = () => albums.className = "albums";
It’s quite annoying to keep clicking the left and right buttons, so we add a keypress handler and catch left and right kepyresses. We get the index of the currently selected image, and if the image that we are intending to go to exists, we click that image’s radio input.
document.onkeydown = (e) => {
if (e.keyCode !== 37 && e.keyCode !== 39) return;
const sp = document.querySelector('input:checked').id.split('.');
const el = document.getElementById(sp[0]+'.'+(parseInt(sp[1]) + (e.keyCode === 37 ? -1 : 1)));
if (el) el.click();
};
});
Devlogs
Because I wanted to write devlogs like this one, I brought the whole project into
jekyll. This lets me write my logs in markdown, which jekyll
converts
directly to html. I bring in styles as necessary to format the code blocks and images on these
pages.
Note: clean this up when you have time.