UNPKG

4.28 kBJavaScriptView Raw
1'use strict';
2
3const basicHTML = require('basichtml');
4const deepmerge = require('deepmerge');
5const clonedeep = require('lodash.clonedeep');
6const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray;
7
8const defaultSettings = {
9 selector: ':not(picture) img[src]:not([srcset]):not([src$=".svg"])',
10 resizedImageUrl: (src, width) =>
11 src.replace(/^(.*)(\.[^\.]+)$/, '$1-' + width + '$2'),
12 runBefore: (image) => image,
13 runAfter: (image) => image,
14 fallbackWidth: 640,
15 minWidth: 320,
16 maxWidth: 2560,
17 steps: 5,
18 sizes: '100vw',
19 classes: [],
20 attributes: {},
21};
22
23const imagesResponsiver = (html, options = {}) => {
24 // Default settings
25 let globalSettings = defaultSettings;
26
27 // Overhide default settings with a "default" preset
28 if (options.default !== undefined) {
29 globalSettings = deepmerge(globalSettings, options.default, {
30 arrayMerge: overwriteMerge,
31 });
32 }
33
34 const { document } = basicHTML.init({
35 selector: {
36 // use the module sizzle, it will be required
37 // automatically
38 name: 'sizzle',
39 // how to retrieve results => querySelectorAll
40 $(Sizzle, element, css) {
41 return Sizzle(css, element);
42 },
43 },
44 });
45
46 document.documentElement.innerHTML = html;
47
48 [...document.querySelectorAll(globalSettings.selector)]
49 .filter((image) => {
50 // filter out images without a src, or not SVG, or with already a srcset
51 return (
52 image.getAttribute('src') !== null &&
53 !image.getAttribute('src').match(/\.svg$/) &&
54 image.getAttribute('srcset') === null
55 );
56 })
57 .forEach((image) => {
58 let imageSettings = clonedeep(globalSettings);
59
60 imageSettings.runBefore(image, document);
61
62 // Overhide settings with presets named in the image classes
63 if ('responsiver' in image.dataset) {
64 // TODO: Merging preset settings to previous settings should be easier
65 image.dataset.responsiver.split(' ').forEach((preset) => {
66 if (options[preset] !== undefined) {
67 let presetClasses = options[preset].classes || [];
68 let existingClasses = imageSettings.classes;
69 imageSettings = deepmerge(imageSettings, options[preset], {
70 arrayMerge: overwriteMerge,
71 });
72 imageSettings.classes = [...existingClasses, ...presetClasses];
73 }
74 });
75 delete image.dataset.responsiver;
76 }
77
78 const imageSrc = image.getAttribute('src');
79
80 let imageWidth = image.getAttribute('width');
81 if (imageWidth !== null) {
82 imageSettings.minWidth = Math.min(imageSettings.minWidth, imageWidth);
83 imageSettings.maxWidth = Math.min(imageSettings.maxWidth, imageWidth);
84 imageSettings.fallbackWidth = Math.min(
85 imageSettings.fallbackWidth,
86 imageWidth
87 );
88 }
89
90 if (imageSettings.classes.length > 0) {
91 image.classList.add(...imageSettings.classes);
92 }
93
94 // Change the image source
95 image.setAttribute(
96 'src',
97 imageSettings.resizedImageUrl(imageSrc, imageSettings.fallbackWidth)
98 );
99
100 // generate the srcset attribute
101 let srcset = [];
102 for (let i = 0; i < imageSettings.steps; i++) {
103 let width = Math.ceil(
104 imageSettings.minWidth +
105 ((imageSettings.maxWidth - imageSettings.minWidth) /
106 (imageSettings.steps - 1)) *
107 i
108 );
109 srcset.push(
110 `${imageSettings.resizedImageUrl(imageSrc, width)} ${width}w`
111 );
112 }
113 image.setAttribute('srcset', srcset.join(', '));
114
115 // add sizes attribute
116 image.setAttribute('sizes', imageSettings.sizes);
117
118 // add 'data-pristine' attribute with URL of the pristine image
119 image.dataset.pristine = imageSrc;
120
121 // Add attributes from the preset
122 if (Object.keys(imageSettings.attributes).length > 0) {
123 for (const attribute in imageSettings.attributes) {
124 if (imageSettings.attributes[attribute] !== null) {
125 image.setAttribute(attribute, imageSettings.attributes[attribute]);
126 }
127 }
128 }
129
130 imageSettings.runAfter(image, document);
131 });
132
133 return document.toString();
134};
135
136module.exports = imagesResponsiver;