Images add a critical element to any website - as a means of communication or simply to set the scene. Finding the right image for your project can be challenging, but serving it should not be. Although the native <img> element is powerful, it doesn't take full advantage of modern advances in image distribution.
For one, there are many better image formats than JPEG - such as WebP. They are smaller and more versatile, and are supported by many modern browsers. Additionally, with devices ranging from tiny androids to the largest desktop screens, there is no one size fits all resolution to provide. Choose too small a resolution and it's blurry on high-definition devices. Choose too large a resolution and mobile users might back out before the image even loads.
Fortunately, Vanilla Plus JS provides a simple preprocessing command to take any image, convert it to the appropriate formats and resolutions, and bundle them into a picture element. This means the browser will choose the best format and resolution for the device, with no additional configuration required.
To get started, simply add the desired image within your
src/public/img/
folder,
and then add the following code to your HTML file to generate
the picture element:
<!--[IMAGE: /img/example.jpg 300 400]-->
This will generate a picture element whose source comes from the post-processed representations of the image, cropped to the desired aspect ratio (in this case, 300x400), and scaled to the appropriate device resolution. If you're curious how this picture element looks or how to style it, just inspect this page or check out the source code.
This accepts more arguments, though the defaults are usually sufficient. The format is:
path width height [cover] [crop_arguments] [lazy OR eager]
Where cover
, lazy
,
and eager
are literal strings. The crop
arguments are specified as a json object. The object must be a subset of the
following, where the values in the example are the default values if not
specified:
{
"crop_left_percentage": 0.5,
"crop_top_percentage": 0.5,
"pre_top_crop": 0,
"pre_left_crop": 0,
"pre_right_crop": 0,
"pre_bottom_crop": 0
}
Where crop_left_percentage
dictates
the proportion to crop from the left when we need to crop the image to
be thinner to match the aspect ratio. The default value of 0.5 means crop
evenly from the left and the right, a value of 0 would mean crop exclusively
from the right. Similarly, crop_top_percentage
dictates the proportion to crop from the top when we need to crop the image
to be shorter to match the aspect ratio.
Further, pre_top_crop
simply
crops the given number of pixels from the top of the image
before applying the standard cover fit algorithm. pre_left_crop
,
pre_right_crop
, and pre_bottom_crop
similarly crop the
given number of pixels from the left, right, and bottom of the
image respectively.
Finally, lazy
and eager
are used
to specify whether
the image should be loaded asynchronously or not. By default, images
are loaded asynchronously on supporting browsers via the
lazy
property, but you may
specify eager
to load the image
before continuing to the next element.
That covers how to use the image preprocessing. For a bit more
detail in how it works, it will sample various compression levels
and resolutions on the first pass. It will prefer lower compression
levels and also lower file sizes. These two things are in conflict,
so the calculation is managed by assigning a "preference" to each
compression level in vanillaplusjs.json
,
and then to compare two compression levels we divide the file size
by the preference to get a penalty for each, then select the
compression level with the lowest penalty. We repeat this for each
resolution.
To avoid edge-cases where very small files are compressed exceptionally
well, a minimum unit size is specified, where there is no decreased
penalty below that file size. Furthermore, the resolution we are
attempting will rule out certain compression levels, and hence the
build time is dramatically reduced by not attempting those. For example,
it will never make sense to compress a 300x300 image to 50% quality,
or to use a lossless format for a 3000x3000 image. And generating that
lossless format to test its size can take upwards of a minute. This is
what the min_area_px2
and max_area_px2
parameters describe.
Processing is done with Pillow, so you can use any image format or compression level supported by that library in the configuration.
Although producing the picture element covers most use-cases for images, it's sometimes convenient to use the preprocessor to generate images which are then converted to HTML code in JavaScript. This is typically for more dynamic pages, where the page layout is generated on the fly.
It would be possible, although painful, to generate hidden picture
elements and then parse the element to determine where the image
is located. However, Vanilla Plus JS provides a direct way to
accomplish this via .images.json
files, which are handled specially by the preprocessor.
Suppose you had a highly dynamic profile page which required a
default avatar image which you want to display via JavaScript
after determining the user does not have a profile picture set.
You could create the following file at
src/public/js/profile.images.json
:
{
"default_avatar": {
"path": "/img/default_avatar.jpg",
"width": 20,
"height": 20,
"crop_style": "cover",
"crop_arguments": {},
"lazy": true
}
}
Then the preprocessor will generate a file at
src/public/js/profile.images.js
with content like the following:
export default {
"default_avatar": {
"target": {
"outputs": {
"jpeg": [
{
"choice": "100",
"height": 20,
"url": "/img/default_avatar/1/20x20.jpeg",
"width": 20
},
{
"choice": "100",
"height": 30,
"url": "/img/default_avatar/1/30x30.jpeg",
"width": 30
}
],
"webp": [
{
"choice": "lossless",
"height": 20,
"url": "/img/default_avatar/1/20x20.webp",
"width": 20
},
{
"choice": "lossless",
"height": 30,
"url": "/img/default_avatar/1/30x30.webp",
"width": 30
}
]
},
"settings": {
"crop": "cover",
"crop_settings": {
"crop_left_percentage": 0.5,
"crop_top_percentage": 0.5,
"pre_bottom_crop": 0,
"pre_left_crop": 0,
"pre_right_crop": 0,
"pre_top_crop": 0
},
"height": 20,
"width": 20
}
}
}
};
Which can be imported into JavaScript as follows:
import images from '/js/profile.images.js';
Furthermore, it will generate a placeholder file at
src/public/js/profile.images.js
with
the appropriate typehints to ensure that it nicely integrates with
your IDE.