Skip to content

Commit

Permalink
feat: add animated gif sample
Browse files Browse the repository at this point in the history
  • Loading branch information
vhf committed May 23, 2024
1 parent c15aeba commit b8a5866
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Here's how the capture works:

An artwork can register other exports than the default one. You can use those to let viewers download images from your artwork instead of implementing a hotkey system (such as *hit `d` to download a PNG*.) The parent of your iframe, which we call *the host*, will know which exports have been registered and show buttons allowing viewers to trigger those exports.

Additionally, an artwork can register a custom thumbnail export. This is particularly interesting for animated GIFs. For thumb exports we strongly recommend an aspect ratio of 1:1 and a size of max 400x400px. Use `thumb: true`.
Additionally, an artwork can register a custom thumbnail export. This is particularly interesting for animated GIFs. For thumb exports we strongly enforce an aspect ratio of 1:1 and strongly recommend a max resolution of 400x400px. Use `thumb: true`.

Example:

Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<body>
<ul>
<li><a href="./sample-host/">Sample host</a> (check source)</li>
<li><a href="./sample-generator/generator.html">Sample generator</a> (check source)</li>
<li><a href="./sample-generator/index.html">Sample generator</a> (check source)</li>
<li>
<a href="./src/snippet.js">snippet</a>
</li>
Expand Down
3 changes: 3 additions & 0 deletions sample-generator-gif/gif.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions sample-generator-gif/gif.worker.js

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions sample-generator-gif/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sample GIF</title>
<script>
var query = new URLSearchParams(window.location.search);

window.$o = {
_exports: {},
_exported: null,
_v: '0.0.1',
capture,
isCapture: query.has('capture'),
registerExport,
registerFeatures,
seed: Math.floor(Math.random() * Date.now()),
};
if (query.has('seed')) {
$o.seed =
parseInt(
query
.get('seed')
.replace(/[^0-9a-f]/gi, 'f')
.padEnd(12, 'f'),
16
) % Number.MAX_SAFE_INTEGER;
query.set('seed', $o.seed.toString(16));
window.history.pushState('', '', '?' + query.toString());
}
$o.rnd = (function splitmix32(a) {
return (state) => {
if (state === null) a = $o.seed;
a |= 0;
a = (a + 0x9e3779b9) | 0;
let t = a ^ (a >>> 16);
t = Math.imul(t, 0x21f0aaad);
t = t ^ (t >>> 15);
t = Math.imul(t, 0x735a2d97);
return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;
};
})($o.seed);

function registerFeatures(features) {
if (typeof features === 'undefined') {
return ($o.features = null);
}
if (typeof features !== 'object' || Array.isArray(features)) {
throw new Error('registerFeatures expects an object');
}
return ($o.features = features);
}

function registerExport(args, fn) {
const err = new Error(`Cannot register exporter for ${JSON.stringify(args)}`);
if (typeof fn !== 'function') throw err;
if (typeof args !== 'object' || Array.isArray(args)) throw err;
if (typeof args.mime !== 'string') throw err;
if (!args.resolution?.x || !args.resolution?.x) throw err;
if ($o._exports[args.mime]) throw err;
if (args.aspectRatio) {
args.resolution.y = args.resolution.x * args.aspectRatio;
}
args = {
mime: args.mime,
aspectRatio: args.aspectRatio,
resolution: args.resolution,
default: !!args.default,
thumb: !!args.thumb,
};

$o._exports[args.mime] = { ...args, fn };
cast('register-export', args);
return true;
}

function cast(msgId, payload) {
[parent, window].forEach((target) => {
try {
target?.postMessage({ ...payload, id: `$o:${msgId}` }, '*');
} catch (_) {}
});
}

async function capture() {
if ($o.isCapture && !$o._exported) {
$o._exported = { status: 'pending' };
const exporter = Object.values($o._exports).find((o) => o.default === true);
if (!exporter) throw new Error(`No default exporter found`);
const exported = await exporter.fn({
resolution: exporter.resolution,
status: 'done',
});
const { resolution, aspectRatio, mime } = exporter;
$o._exported = { mime, resolution, aspectRatio, exported };
cast('captured', { ...$o._exported });
}
}

window.addEventListener('message', (e) => {
if (e.data.id === '$o:export') {
const exporter = $o._exports[e.data.mime];
console.log(e.data);
exporter?.fn(e.data).then((exported) => {
cast('exported', { ...e.data, exported });
});
}
});
</script>
<script src="./gif.js"></script>
<script src="./index.js"></script>
<style>
#my-canvas {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 2px solid blue;
}
</style>
</head>
<body>
<canvas id="my-canvas" width="300" height="300"></canvas>
</body>
</html>
Loading

0 comments on commit b8a5866

Please sign in to comment.