211 lines
6.0 KiB
JavaScript
211 lines
6.0 KiB
JavaScript
/*
|
|
|
|
HTML5 Reader for Electric Zine Maker, made by Jeremy Oduber & contributors 2019-2021
|
|
v21.5
|
|
Me:
|
|
https://twitter.com/JeremyOduber
|
|
This:
|
|
https://jeremyoduber.itch.io/js-zine
|
|
Electric Zine Maker:
|
|
https://alienmelon.itch.io/electric-zine-maker
|
|
GitHub:
|
|
https://github.com/jeremyoduber/EZM-Reader
|
|
Licensed under the MIT License:
|
|
https://github.com/jeremyoduber/EZM-Reader/blob/main/LICENSE
|
|
|
|
*/
|
|
|
|
//---- USER OPTIONS ----//
|
|
const TEMPLATE = 1; // Change this value to set the template
|
|
/*
|
|
Available templates:
|
|
|
|
1: 8 pages (default)
|
|
8 Page Folded Zine
|
|
8 Page Z-Fold
|
|
Quarter Size
|
|
2: 12 pages
|
|
Fancy T-Cut Zine
|
|
3: 14 pages
|
|
Easy Long Cut
|
|
4: 16 pages
|
|
16 Page Micro-Mini
|
|
Mini-Booklet
|
|
Fancy Flapbook
|
|
5: 24 pages
|
|
Tetraflexagon
|
|
6: 26 pages
|
|
Square Accordion
|
|
Normal Accordion
|
|
7: 32 pages
|
|
Mini-Mini-Booklet
|
|
8: 64 pages
|
|
Micro
|
|
*/
|
|
const BGCOLOR = '#f5f5f5'; // Change this hex value to set the background color. Remember to keep the quotes!
|
|
const ALT = 'Reader for Electric Zine Maker'; // Change this to a plaintext copy or description of your content to make it visible to screen-readers
|
|
const SMOOTH = true; // Set to false if you want crispy pixels. Leave true if you like the blur.
|
|
|
|
//---- END USER OPTIONS ----//
|
|
|
|
// Setup constants and variables
|
|
const FOV = 45;
|
|
const LOADING_OVERLAY = document.querySelector('#loading');
|
|
let card_amount;
|
|
let current_state = 0;
|
|
let textures = [];
|
|
let pages = [];
|
|
|
|
document.body.style.background = BGCOLOR;
|
|
document.body.ariaLabel = ALT;
|
|
if (SMOOTH) {
|
|
document.body.style.imageRendering = 'auto';
|
|
} else {
|
|
document.body.style.imageRendering = 'pixelated';
|
|
}
|
|
const metaTheme = document.createElement('meta');
|
|
metaTheme.name = 'theme-color';
|
|
metaTheme.content = BGCOLOR;
|
|
document.head.appendChild(metaTheme);
|
|
|
|
function getTextures(num) {
|
|
return ['pages/FRONT.png', 'pages/INNERFRONT.png'].concat(
|
|
new Array(num).fill().map((_, idx) => 'pages/' + (idx + 1) + '.png'),
|
|
['pages/BACK.png']
|
|
);
|
|
}
|
|
|
|
// Select template
|
|
switch (TEMPLATE) {
|
|
default:
|
|
case 1:
|
|
card_amount = 4;
|
|
textures = getTextures(5);
|
|
break;
|
|
case 2:
|
|
card_amount = 6;
|
|
textures = getTextures(9);
|
|
break;
|
|
case 3:
|
|
card_amount = 7;
|
|
textures = getTextures(11);
|
|
break;
|
|
case 4:
|
|
card_amount = 8;
|
|
textures = getTextures(13);
|
|
break;
|
|
case 5:
|
|
card_amount = 12;
|
|
textures = getTextures(21);
|
|
break;
|
|
case 6:
|
|
card_amount = 13;
|
|
textures = getTextures(23);
|
|
break;
|
|
case 7:
|
|
card_amount = 16;
|
|
textures = getTextures(29);
|
|
break;
|
|
case 8:
|
|
card_amount = 32;
|
|
textures = getTextures(61);
|
|
break;
|
|
}
|
|
|
|
// Preloader
|
|
Promise.all(
|
|
textures.map(
|
|
src =>
|
|
new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
img.onload = () => resolve(img);
|
|
img.onerror = reject;
|
|
img.src = src;
|
|
img.alt = src.split('/')[1].split('.')[0];
|
|
})
|
|
)
|
|
)
|
|
.then(imgs => {
|
|
LOADING_OVERLAY.remove();
|
|
const list = document.createElement('ul');
|
|
list.ariaHidden = true;
|
|
pages = imgs.map((img, idx) => {
|
|
const li = document.createElement('li');
|
|
const flip = idx % 2;
|
|
li.className = 'depth-' + Math.min(2, idx);
|
|
li.style.transform = 'translateX(100%) rotateY(0deg) scaleZ(' + (flip ? -1 : 1) + ')';
|
|
li.appendChild(img);
|
|
list.appendChild(li);
|
|
return li;
|
|
});
|
|
document.body.appendChild(list);
|
|
|
|
function updatePerspective() {
|
|
const w = window.innerWidth;
|
|
const h = window.innerHeight;
|
|
list.style.perspective = Math.sqrt(((w / 2) * w) / 2 + ((h / 2) * h) / 2) / Math.tan(((FOV / 2) * Math.PI) / 180) + 'px';
|
|
}
|
|
|
|
window.addEventListener('resize', updatePerspective);
|
|
updatePerspective();
|
|
})
|
|
.catch(error => {
|
|
console.error(error);
|
|
LOADING_OVERLAY.textContent = 'Something went wrong! Make sure your images are in the pages folder! See console for details.';
|
|
});
|
|
|
|
// Keyboard input
|
|
document.addEventListener('keyup', function onKeyUp(key) {
|
|
if (key.key === 'ArrowLeft' || key.key === 'a') {
|
|
flipLeft();
|
|
} else if (key.key === 'ArrowRight' || key.key === 'd') {
|
|
flipRight();
|
|
}
|
|
});
|
|
|
|
// Mouse input
|
|
document.addEventListener('pointerup', function onPointerUp(event) {
|
|
if (event.button !== 0) return;
|
|
if (event.clientX < window.innerWidth / 2) {
|
|
flipRight();
|
|
} else {
|
|
flipLeft();
|
|
}
|
|
});
|
|
|
|
function getPages(state) {
|
|
return [pages[state * 2], pages[state * 2 + 1]].filter(i => i);
|
|
}
|
|
function replaceTransformPerPage(state, search, replace) {
|
|
getPages(state).forEach(page => {
|
|
page.style.transform = page.style.transform.replace(search, replace);
|
|
});
|
|
}
|
|
function setDepth(state, depth) {
|
|
getPages(state).forEach(page => {
|
|
page.className = page.className.replace(/depth-\d+/, 'depth-' + Math.min(depth, 2));
|
|
});
|
|
}
|
|
|
|
// Flip page left
|
|
function flipLeft() {
|
|
if (current_state >= card_amount) return;
|
|
replaceTransformPerPage(current_state, '0deg', '-180deg');
|
|
setDepth(current_state - 1, 1);
|
|
setDepth(current_state - 2, 2);
|
|
setDepth(current_state + 1, 0);
|
|
setDepth(current_state + 2, 1);
|
|
++current_state;
|
|
}
|
|
|
|
// Flip page right
|
|
function flipRight() {
|
|
if (current_state <= 0) return;
|
|
replaceTransformPerPage(current_state - 1, '-180deg', '0deg');
|
|
setDepth(current_state - 3, 1);
|
|
setDepth(current_state - 2, 0);
|
|
setDepth(current_state + 1, 2);
|
|
setDepth(current_state, 1);
|
|
--current_state;
|
|
}
|