Parcourir la source

first draft commit

Casey DeLorme il y a 5 ans
commit
81a058ad0b
3 fichiers modifiés avec 222 ajouts et 0 suppressions
  1. 55 0
      demo.html
  2. 53 0
      readme.md
  3. 114 0
      show.js

+ 55 - 0
demo.html

@@ -0,0 +1,55 @@
+<!doctype html>
+<html lang='en'>
+<head>
+    <meta charset='utf-8'>
+    <meta name='viewport' content='width=device-width'>
+    <title>Show</title>
+    <style type='text/css'>
+        * { margin: 0; padding: 0; }
+        html, body, main { height: 100%; width: 100%; }
+        body { background: #000; }
+        video, img { display: block; margin: 0 auto; cursor: none; }
+        .fillwidth { width: 100%; height: auto; }
+        .fillheight { height: 100%; width: auto; }
+        footer { display: none; height: 0; width: 0; }
+    </style>
+</head>
+<body>
+    <main></main>
+    <footer>
+        <script src='show.js'></script>
+        <script>
+            let files = [
+                'image.png',
+                'image.jpg',
+                {
+                    file: 'animated.gif',
+                    delay: 960*5,
+                },
+                {
+                    file: 'substitute/%s.png',
+                    range: [4, 9],
+                },
+                {
+                    file: 'substitutev2/%s.png',
+                    range: ["one", "two", "three", "four"],
+                },
+                {
+                    file: 'prefixed-group/%03s.png',
+                    range: [1, 200],
+                    delay: 200,
+                },
+                {
+                    file: 'video.webm',
+                    delay: 3,
+                },
+                {
+                    file: 'video.mp4',
+                    delay: 5,
+                },
+            ];
+            let s = new Show(document.getElementsByTagName('main')[0], files, 1500);
+        </script>
+    </footer>
+</body>
+</html>

+ 53 - 0
readme.md

@@ -0,0 +1,53 @@
+
+# [show](https://git.caseydelorme.com/cdelorme/show)
+
+A system for rendering sequences of various media, including images and videos.
+
+It leverages `addEventListener` for preloading, and `requestAnimationFrame` for real-time rendering.
+
+_This is an optimized and simplified replacement for [jslideshow](https://github.com/cdelorme/jslideshow)._
+
+
+## usage
+
+The [`demo.html`](demo.html) included demonstrates full screen styles, importing, and loading a set of files.
+
+A new instance of `Show` accepts three parameters:
+
+- parent element to insert the slideshow
+- array of files in two different formats
+- default duration or delay per displayed media
+
+The parent element will have its children replaced.
+
+There are two formats for files.
+
+- A string
+- An object
+
+The string is treated verbatim and assigned the default delay value.
+
+The only required value is `file`.
+
+The object format supports an optional `delay` as an override.
+
+If you are working with a group of files then the `range` array property is useful.
+
+As an array it supports two numbers as minimum and maximum, or a set of string substitutes.
+
+When `range` is present the `file` property will be treated as as "format string", which supports these formats:
+
+- a direct string replacement (eg. `%s`)
+- a left pad (eg. `%03s` creating `001`, `002`, `003`, etc)
+
+_Substitution only supports individual characters not whole patterns, but they do **not** need to be numbers._
+
+**If the file is a video it will substitute the default `delay` for the video duration, otherwise it will treat the delay as a multiplier allowing the video to loop a specified number of times.**
+
+This system supports video files with the extensions `mp4`, `webm`, and `ogg`.  _Due to browser behavior (namely Google Chrome), videos will be muted._
+
+The system preloads all files and beings playing automatically.
+
+It provides a `toggle()` function to change its state.
+
+If the target window is not the active window the loop will be halted.

+ 114 - 0
show.js

@@ -0,0 +1,114 @@
+"use strict";
+(() => {
+	function Show(parent, files, delay) {
+		this.index = 0;
+		this.elapsed = 0;
+		this.playing = true;
+		this.loaded = false;
+		this.list = [];
+		this.updated = Date.now();
+		this.delay = !isNaN(delay) ? delay : 3000;
+		this.parent = parent;
+		this.parent.innerHTML = '';
+		this.parent.appendChild(document.createElement('span'));
+		(async () => {
+			await this.parse(files);
+			window.requestAnimationFrame(() => { this.render(); });
+			window.addEventListener('keyup', (e) => {
+				if (!e.keyCode) return;
+				if (e.keyCode === 32) { this.toggle(); }
+				else if (e.keyCode === 37) { this.prev(); }
+				else if (e.keyCode === 39) { this.next(); }
+			}, false);
+		})();
+	};
+
+	Show.prototype.toggle = function() { this.playing = !this.playing; };
+
+	Show.prototype.next = function() {
+		if (!this.loaded) return;
+		if (++this.index >= this.list.length) this.index = 0;
+		this.elapsed = 0;
+	 };
+
+	Show.prototype.prev = function() {
+		if (!this.loaded) return;
+		if (--this.index < 0) this.index = this.list.length-1; this.elapsed = 0;
+	};
+
+	Show.prototype.format = function(f, a) {
+		let start = f.indexOf('%');
+		if (f.length > start && f[start+1] === 's') {
+			return f.replace('%s', a);
+		} else if (f.length > start+3 && f[start+3] === 's') {
+			let c = String(f[start+1]);
+			let w = parseInt(f[start+2]);
+			let r = (a.length >= w) ? a : (new Array(w).join(c) + a).slice(-w);
+			return f.replace(f.slice(start, start+4), r);
+		}
+		return f;
+	};
+
+	Show.prototype.preload = async function(file, delay) {
+		const o = { p: file };
+		if (o.p.endsWith('.mp4') || o.p.endsWith('.webm') || o.p.endsWith('.ogg')) {
+			let e = document.createElement('video');
+			e.addEventListener('error', () => { this.list.splice(this.list.indexOf(o), 1); }, {once: true});
+			e.addEventListener('canplay', () => { o.d = (isNaN(delay) ? 1 : delay) * e.duration * 1000; o.e = e; }, {once: true});
+			e.muted = true;
+			e.loop = true;
+			e.src = o.p;
+		} else {
+			let e = document.createElement('img');
+			e.addEventListener('error', () => { this.list.splice(this.list.indexOf(o), 1); }, {once: true});
+			e.addEventListener('load', () => { o.e = e; }, {once: true});
+			o.d = isNaN(delay) ? this.delay : delay;
+			e.src = o.p;
+		}
+		this.list.push(o);
+	}
+
+	Show.prototype.parse = async function(files) {
+		if (!(files instanceof Array)) return;
+		files.map((o) => {
+			if (typeof o === 'string') {
+				this.preload(o);
+			} else if (o instanceof Object && !(o instanceof Array) && typeof o.file !== 'undefined' && typeof o.file === 'string') {
+				if (o.file.indexOf('%') > -1 && typeof o.range !== 'undefined' && o.range instanceof Array) {
+					if (o.range.length === 2 && !(isNaN(o.range[2]) && isNaN(o.range[1])) && parseInt(o.range[0]) < parseInt(o.range[1])) {
+						for (let i = parseInt(o.range[0]); i <= parseInt(o.range[1]); i++) {
+							this.preload(this.format(o.file, i), o.delay);
+						}
+					} else {
+						for (let i of o.range) {
+							this.preload(this.format(o.file, i), o.delay);
+						}
+					}
+				} else {
+					this.preload(o.file, o.delay);
+				}
+			}
+		});
+	};
+
+	Show.prototype.render = function() {
+		let d = Date.now();
+		if (this.loaded) {
+			let o = this.list[this.index];
+			let vid = (o.e.tagName == 'VIDEO') ? 'video' : 'natural';
+			o.e.className = (o.e[vid+'Width'] / o.e[vid+'Height'] < this.parent.clientWidth / this.parent.clientHeight) ? 'fillheight' : 'fillwidth';
+			if (this.parent.firstChild !== o.e) {
+				o.e.src = o.e.src;
+				this.parent.replaceChild(o.e, this.parent.firstChild);
+				if (vid === 'video') o.e.play();
+			}
+			if (this.playing && document.hasFocus() && (this.elapsed += (d - this.updated)) && this.elapsed >= o.d) this.next();
+		} else {
+			this.loaded = (this.list.length > 0) && this.list.every((l) => { return typeof l.e !== 'undefined'; });
+		}
+		s.updated = d;
+		window.requestAnimationFrame(() => { this.render(); });
+	}
+
+	window.Show = Show;
+})();