Thirteen years ago I wrote a simple Javascript “slideshow” web app that preloaded images and was easily viewable on the tiniest of displays, such as my favorite microwave transceiver at the time, my Blackberry Bold 9780, with its 2.44 inch, 480×360 pixel @ 244 ppi display.
At the time, Javascript was fairly mature, almost old enough to graduate from high school. ECMAScript 5 had been released 3 years previous, although it was yet to be supported in most of the major browsers, which didn’t happen until around 2014. InternetExplorer was still Microsoft’s flagship, and like it or not, web developers needed to make sure whatever they wrote supported it. Although Node.js was widely in use, many JavaScript frameworks (React, Svelte, Vue), were only a twinkle in the eye of Jordan Walke, Rich Harris, or Evan You. Angular, JQuery, MooTools, and Prototype however, had been around for a few years, and were powerful widely-adopted frameworks for building more complex, browser-agnostic applications. In fact, JQuery still has an active release pipeline today.
I revisited my JAJavaSSS application (click on any of the links under “name,” then any of the thumbnail images) by just reusing whatever still worked, cleaning up the CSS, and porting old hacks to newer ones that modern JavaScript features implement out-of-the-box.
export class Jajavasss
{
/* TODO: decouple magic numbers and strings, make this less dependent on DOM-specifics */
aspect;
auto;
container_aspect = 1;
els;
imgs;
interval;
lID;
load_cnt;
num_of_slides;
img_url;
refit_timeout;
slide_num = 0;
tID;
wait;
constructor( auto, aspect, const_aspect, fastmode, img_url)
{
this.els = { "controls":null, "copy":null, "picblock":null};
this.load_cnt = 0;
this.lID = null;
this.tID = null;
this.auto = auto;
this.aspect = aspect;
this.const_aspect = const_aspect;
this.fastmode = fastmode;
this.img_url = img_url;
this.num_of_slides = this.img_url.length;
this.imgs = Array( this.num_of_slides);
this.wait = auto ? 1500 : 600;
for( let _name of [ "automate", "firstslide", "lastslide",
"nextslide", "prevslide", "refitBlocks",
"resized", "toggleauto"
])
this[ _name] = this[ _name].bind( this);
}
setInterval( i)
{
this.interval = i;
}
firstslide()
{
if( ! this.auto) {
this.slide_num = 0;
this.changeslide();
}
}
prevslide()
{
if( ! this.auto) {
this.slide_num = this.slide_num - 1;
if( this.slide_num < 0)
this.slide_num = 0;
this.changeslide();
}
}
nextslide( calledBy)
{
if( this.slide_num == this.num_of_slides - 1) {
this.auto = false;
const chkbox_el = document.getElementById( "autoOn");
if( chkbox_el) chkbox_el.checked = false;
} else {
if( ( this.auto) && ( calledBy == "usr")) {
return;
}
this.slide_num = this.slide_num + 1;
this.changeslide();
if( this.auto) {
this.tID = setTimeout( this.nextslide, this.interval);
}
}
}
lastslide()
{
if( ! this.auto) {
this.slide_num = this.num_of_slides - 1;
this.changeslide();
}
}
refitSlide()
{
document.picbox.style = ( this.aspect[ this.slide_num] > this.container_aspect)
? "width: 98cqw;" : "height: 98cqh;";
}
changeslide()
{
if( ! this.const_aspect)
this.refitSlide()
document.picbox.src = this.img_url[ this.slide_num];
}
toggleauto( checked)
{
if( checked) {
this.auto = true;
this.automate();
} else {
clearTimeout( this.tID);
this.auto = false;
}
}
automate()
{
this.tID = setTimeout( this.nextslide, this.interval);
}
slidesReady()
{
clearTimeout( this.lID);
this.lID = null;
document.getElementById( "content").style.display = "block";
document.getElementById( "loading").style.display = "none";
}
isLoaded()
{
if( ++this.load_cnt >= ( this.num_of_slides - 1)) {
this.slidesReady();
}
}
preLoad()
{
this.lID = setTimeout( () =>
{
console.info( "preLoad: timed out, falling back to slidesReady()");
this.slidesReady();
}, 300 * this.num_of_slides);
for( let i = 0; i < this.num_of_slides && this.lID != null; i++) {
this.imgs[ i] = new Image();
this.imgs[ i].src = this.img_url[ i];
this.imgs[ i].onLoad = this.isLoaded();
}
}
async load()
{
//window.focus();
var loc_str = window.location + "";
const URL_array = loc_str.split( /\?/);
var query_str = URL_array[ URL_array.length - 1];
const params = query_str.split( /&/);
for( let i = 0; i < params.length; i++) {
if( params[ i].match( /start=/)) {
this.slide_num = parseInt( params[ i].replace( /start=/, ""));
}
}
this.setInterval( this.fastmode ? 100 : this.wait); // ~ 5 images par seconde ;-}
this.preLoad();
this.changeslide();
window.moveTo( window.screen.left, 0);
if( windIsInPopup()) {
let resized = false;
const listener = () => ( resized = true);
window.addEventListener( "resize", listener);
// loop & sleep is a workaround for why sometimes
// resizeTo() does not seem to work even where it's supported
const w = Math.min( window.screen.availWidth, 720);
const h = Math.min( window.screen.availHeight, 720);
for( let times=0; ( times<3 && !resized); times++) {
console.log( `_load(): resize window try #${times+1}`);
window.resizeTo( w, h); // try again
await sleep( 999);
}
window.removeEventListener( "resize", listener);
}
for( let p in this.els)
this.els[ p] = document.getElementById( p);
this.refitBlocks();
window.addEventListener( "resize", this.resized);
if( this.auto)
setTimeout( this.automate, 567);
document.body.style.opacity = "1"
}
resized()
{
// more debounce to the ounce..
if( this.refit_timeout)
clearTimeout( this.refit_timeout);
this.refit_timeout = setTimeout( this.refitBlocks, 123);
}
refitBlocks()
{
console.log( Date.now(), "refitBlocks()");
if( this.refit_timeout)
this.refit_timeout = undefined;
const avail_w = this.els.picblock.getBoundingClientRect().width;
const avail_h = window.innerHeight - this.els.copy.getBoundingClientRect().height
- this.els.controls.getBoundingClientRect().height - 20; // wiggle-room
this.els.picblock.style.height = `${avail_h}px`;
this.container_aspect = avail_w / avail_h;
if( this.const_aspect) {
if( this.const_aspect > this.container_aspect) {
this.els.picblock.classList.remove( "tall");
this.els.picblock.classList.add( "wide");
} else{
this.els.picblock.classList.remove( "wide");
this.els.picblock.classList.add( "tall");
}
} else{
this.refitSlide();
}
}
}
The class can be used like this:
import { Jajavasss } from "./jajavasss.js";
var img_url = Array( 80);
var aspect = Array( 80);
var auto = true;
const fastmode = true;
img_url[ 0] = "/photos/snow_flurry_bunnies/450_snow0001.jpg";
img_url[ 1] = "/photos/snow_flurry_bunnies/450_snow0002.jpg";
// ...
img_url[ 79] = "/photos/snow_flurry_bunnies/450_snow0080.jpg";
const const_aspect = 0.57;
const slideshow = new Jajavasss( auto, aspect, const_aspect, fastmode, img_url);
function changeSpeed()
{
const targ = this.querySelector( "input[name='speed']:checked");
if( ! targ) {
console.error( "oops");
return;
}
let ms = 9876;
switch( targ.value) {
case "slow": ms = 4000; break;
case "fast": ms = 2000; break;
case "mach": ms = 600; break;
}
slideshow.setInterval( ms);
}
function _load()
{
slideshow.load();
if( auto) // DOM loaded with auto=true from queryParam
return;
// setup controls if not in full auto
for( let _name of [ "firstslide", "prevslide", "nextslide", "lastslide"])
document[ _name].addEventListener( "click", () => slideshow[ _name]( "usr"));
document.getElementById( "speed").addEventListener( "change", changeSpeed);
const ta_el = document.getElementById( "autoOn");
ta_el.addEventListener(
"change", () => slideshow.toggleauto( ta_el.checked)
);
}
window.addEventListener( "DOMContentLoaded", _load);
I know that “popups” created by window.open (the old way) have been generally deprecated for a while (in favor of <dialog> or custom modals, because most browsers can and do block them; they can be annoying to the user, break the flow of the application, etc,.. But I wanted to see if I could make the old popup work and display my slideshow on a variety of displays, including mobile phones in landscape or portrait seamlessly, shrinking or expanding the images as necessary.
Image URLS and a handful of other constant variables are injected by the backend PHP script, which processes an “album/” directory on the host, calculating aspect ratios and serving up thumbnails and “full-size” images. The full-size images are scaled via CSS. I clamp maximum scaled image size somewhat by setting the initial size of the popup to 720px, although if the new window opens in a tab (always the case in mobile), then it will be whatever size the display is. One improvement would be to provide pre-scaled images in steps, i.e.; a 360 width for window sizes up to 480, a 600 width for 480 to 720 displays, a 960 width for 720 and up, etc,.. to improve speed and efficiency on the front-end and possibly, displayed image quality. In fact in looks like browser-support exists for this already: MDN img:sizes and MDN img:srcset 🤣








