diff --git a/packages/www/public/features/iframe/index.md b/packages/www/public/features/iframe/index.md
index f739516..0e7ab31 100644
--- a/packages/www/public/features/iframe/index.md
+++ b/packages/www/public/features/iframe/index.md
@@ -3,5 +3,5 @@ title: iframe
jampack: "--onlyoptim"
---
-`jampack` lazy load iframes [below the fold](/features/optimize-above-the-fold/).
+`jampack` lazy loads iframes below [the fold](/features/optimize-above-the-fold/).
diff --git a/packages/www/public/features/video/index.md b/packages/www/public/features/video/index.md
new file mode 100644
index 0000000..76bc6fd
--- /dev/null
+++ b/packages/www/public/features/video/index.md
@@ -0,0 +1,16 @@
+---
+title: Video
+jampack: "--onlyoptim"
+---
+
+`jampack` optimize videos below [the fold](/features/optimize-above-the-fold/).
+
+## Autoplay videos
+
+Videos below the fold with attribute `autoplay` are lazy loaded using JavaScript.
+
+## Click-to-play videos
+
+Videos without `autoplay` and with a `poster` get a `preload="none"` attribute to postpone the loading of the video until user request.
+
+As of today, `jampack` doesn't automatically create posters for video. It's a TODO.
diff --git a/packages/www/public/features/video/source/index.html b/packages/www/public/features/video/source/index.html
new file mode 100644
index 0000000..bb52cef
--- /dev/null
+++ b/packages/www/public/features/video/source/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Lazy load videos below the fold
+
+
+ The fold
+
+
+
diff --git a/packages/www/src/config.ts b/packages/www/src/config.ts
index 5fa17f5..3298e69 100644
--- a/packages/www/src/config.ts
+++ b/packages/www/src/config.ts
@@ -58,6 +58,7 @@ export const featuresDirs = [
'embed-small-images',
'images-max-width',
'inline-critical-css',
+ 'video',
'iframe',
'prefetch-links',
'browser-compatibility',
diff --git a/src/config-default.ts b/src/config-default.ts
index e2c9582..f36d2ef 100644
--- a/src/config-default.ts
+++ b/src/config-default.ts
@@ -67,6 +67,12 @@ const default_options: Options = {
how: 'native',
},
},
+ video: {
+ autoplay_lazyload: {
+ when: 'below-the-fold',
+ how: 'js',
+ },
+ },
misc: {
prefetch_links: 'off',
},
diff --git a/src/config-types.ts b/src/config-types.ts
index 3b88b27..921f385 100644
--- a/src/config-types.ts
+++ b/src/config-types.ts
@@ -77,6 +77,16 @@ export type Options = {
| 'js'; // Using IntersectionObserver. Requires ~1Ko of JS but is more precise than native lazyload
};
};
+ video: {
+ autoplay_lazyload: {
+ // Only for videos with autoplay
+ when: // Default: 'below-the-fold'
+ | 'never' // All video are loaded eagerly
+ | 'below-the-fold' // videos are lazy loaded only if they are below the fold
+ | 'always'; // Not recommended
+ how: 'js'; // Using IntersectionObserver. Requires ~1Ko of JS
+ };
+ };
misc: {
prefetch_links: 'in-viewport' | 'off';
};
diff --git a/src/optimize.ts b/src/optimize.ts
index 9aa8abc..7c7ab73 100755
--- a/src/optimize.ts
+++ b/src/optimize.ts
@@ -19,6 +19,7 @@ import { inlineCriticalCss } from './optimizers/inline-critical-css.js';
import { prefetch_links_in_viewport } from './optimizers/prefetch-links.js';
import { GlobalState } from './state.js';
import { processIframe } from './optimizers/process-iframe.js';
+import { processVideo } from './optimizers/process-video.js';
const UNPIC_DEFAULT_HOST_REGEX = /^https:\/\/n\//g;
const ABOVE_FOLD_DATA_ATTR = 'data-abovethefold';
@@ -46,97 +47,36 @@ async function analyse(state: GlobalState, file: string): Promise {
const theFold = getTheFold($);
- const imgs = $('img');
- const imgsArray: cheerio.Element[] = [];
- imgs.each((index, imgElement) => {
- imgsArray.push(imgElement);
- });
-
- // Process images sequentially
- const spinnerImg = ora({ prefixText: ' ' }).start();
- for (let i = 0; i < imgsArray.length; i++) {
- const imgElement = imgsArray[i];
-
- spinnerImg.text = kleur.dim(
- ` [${i + 1}/${imgsArray.length}] ${$(imgElement).attr('src')}`
- );
-
- const img = $(imgElement);
- const isAboveTheFold = isElementAboveTheFold(img, imgElement, theFold);
- img.removeAttr(ABOVE_FOLD_DATA_ATTR);
-
- try {
- await processImage(state, file, img, isAboveTheFold);
- } catch (e) {
- state.reportIssue(file, {
- type: 'erro',
- msg:
- (e as Error).message ||
- `Unexpected error while processing image: ${JSON.stringify(e)}`,
- });
- }
- }
-
- // Reset spinner
- spinnerImg.text = kleur.dim(
- ` [${imgsArray.length}/${imgsArray.length}]`
+ await processTag(
+ state,
+ file,
+ $,
+ 'img',
+ 'src',
+ processImage,
+ theFold,
+ appendToBody
);
-
- // Notify issues
- const issues = state.issues.get(file);
- if (issues) {
- spinnerImg.fail();
- console.log(
- kleur.red(` ${issues.length} issue${issues.length > 1 ? 's' : ''}`)
- );
- } else {
- spinnerImg.succeed();
- }
-
- const iframes = $('iframe');
- const iframesArray: cheerio.Element[] = [];
- iframes.each((index, ifElement) => {
- iframesArray.push(ifElement);
- });
-
- // Process iframes sequentially
- const spinnerIframe = ora({ prefixText: ' ' }).start();
- for (let i = 0; i < iframesArray.length; i++) {
- const ifElement = iframesArray[i];
-
- spinnerIframe.text = kleur.dim(
- `