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( - `