Is it possible to simply (and efficiently) publish images and short videos on a website? This is the question we want to explore in this post about TwicPics Components.
Introduction
TwicPics is a SaaS for responsive image services. It provides optimized, perfectly sized, and context-aware media from a single high-resolution version. All that's left to do is display this media in the browser. This is precisely the part that TwicPics Components handles.
TwicPics Components is a free and open-source web component collection that replaces img
and video
tags.
<!-- a classical img usage -->
<img src="path/to/image" />
<!-- a TwicPics img component usage -->
<TwicImg src="path/to/image" />
Available for the most popular frameworks (Angular, React, Gatsby, Next.js, Svelte,Vue.js (version 2), Vue.js (version 3), Nuxt.js, and native Web Components), they allow you to integrate the TwicPics solution quickly and easily into your projects.
The only requirement to use them is to have a TwicPics account. If you don't already have one, you can easily create your own TwicPics account for free.
In the first part of this post, we will write, step by step, the code needed to overcome the various difficulties related to publishing media in a browser: lazy loading, CLS optimization, respect of aspect-ratio, filling option, LQIP (Low-Quality Image Placeholders).
In the second part, we will see how easy it is to do the same thing (and even more) by using TwicPics Components.
Part One - Displaying image without TwicPics Components
We consider here only images. For videos, simply replace <img>
with <video>
.
The image used in this section is https://assets.twicpics.com/examples/cat_1x1.jpg (jpg - 1561x1561px - 428kB).
Layout-Driven Pattern from scratch
Let's start by loading the image into the browser.
We use the TwicPics Script and the data-twic-src
property instead of the native src
attribute.
<!-- Requested: jpg - 1561x1561px - 428kB -->
<!-- Received: webP - 300 x 300 px - 7.2 kB -->
<img data-twic-src="image:cat_1x1.jpg" width="300px" height="300px">
<!-- Installation of TwicPics. That's it, you're all set! -->
<script src="https://demo.twic.pics/?v1" async defer></script>
We can improve this snippet by implementing a primary level of LQIP.
We tell the browser to load a lightweight blurry placeholder first by using the output=preview API transformation and to replace it with the pixel-perfect version later.
<img
data-twic-src="image:cat_1x1.jpg"
src="https://demo.twic.pics/cat_1x1.jpg?twic=v1/output=preview"
width="300px"
height="300px"
>
<!-- Installation of TwicPics -->
<script src="https://demo.twic.pics/?v1" async defer></script>
At this stage, we load an image that is perfectly sized for the display area, ideally compressed and with a working preview.
But as its size is fixed, it is not responsive.
Let's modify our snippet so that the image occupies the full size of its potential container: aka the layout-driven pattern.
<img
data-twic-src="image:cat_1x1.jpg"
src="https://demo.twic.pics/cat_1x1.jpg?twic=v1/output=preview"
>
<!-- Installation of TwicPics -->
<script src="https://demo.twic.pics/?v1" async defer></script>
img {
display:block;
width:100%;
height:100%;
}
Let's test it in the example below.
<!-- Container size set to 200 px wide -->
<div style="width:200px;">
<img
data-twic-src="image:cat_1x1.jpg"
src="https://demo.twic.pics/cat_1x1.jpg?twic=v1/output=preview"
>
</div>
<!-- Container size set to 300px px wide -->
<div style="width:300px;">
<img
data-twic-src="image:cat_1x1.jpg"
src="https://demo.twic.pics/cat_1x1.jpg?twic=v1/output=preview"
>
</div>
<!-- Container size set to 400x200 px -->
<div style="width:400px;height:200px">
<img
data-twic-src="image:cat_1x1.jpg"
src="https://demo.twic.pics/cat_1x1.jpg?twic=v1/output=preview"
>
</div>
Here is the result:
The network console shows us that we have loaded 4 images.
The first one corresponds to the preview.
The 3 others correspond to the images returned by the TwicPics SaaS.
What happened here?
1 - the script understood the display context and asked for the single master image to be resized to fill the 3 containers. This is the good news.
2 - the resizing of the third image brings back a distorted image. We need to specify the filling method (cover or contain).
Let's use object-fit
to indicate how the image should fit into its container.
We set cover
as the default and add a CSS class so we can choose the contain
fill option from our template.
img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.contain{
object-fit: contain;
}
Let's test our modifications with this new example:
<!-- Container size set to 200 px wide -->
<div style="width:200px;">
<img
data-twic-src="image:cat_1x1.jpg"
src="https://demo.twic.pics/cat_1x1.jpg?twic=v1/output=preview"
>
</div>
<!-- Container size set to 400x200 px -->
<div style="width:400px;height:200px">
<img
data-twic-src="image:cat_1x1.jpg"
src="https://demo.twic.pics/cat_1x1.jpg?twic=v1/output=preview"
>
</div>
<!-- Container size set to 400x200 px -->
<div style="width:400px;height:200px">
<!-- Sets object-fit to contain -->
<img class="contain"
data-twic-src="image:cat_1x1.jpg"
src="https://demo.twic.pics/cat_1x1.jpg?twic=v1/output=preview"
>
</div>
The TwicPics script automatically applied the cover
or contain
transformations depending on the display context.
We now have an image displayed without distortion and whose rendering is controlled by the layout. As we want to manage responsive images, we should not set the size of the display area.
Here is a Codepen to test our quick snippet and see how much work remains to be done.
Feel free to look at the network console and resize your browser to see what's happening.
We still have 2 serious problems. We don't manage the aspect ratio nor the CLS optimization.
That's what we are going to fix here.
Layout Driven Pattern + Aspect Ratio Boxes
It's time to integrate the concept of CSS aspect ratio boxes, aka the padding trick
.
This CSS
hint allows us to manage the aspect ratio (even on old browsers) and optimize the CLS. Of course, you could also use the aspect-ratio
CSS property if you target modern browsers only.
<main>
<!-- Empty div but with a reserved 1:1 aspect-ratio area -->
<div class="wrapper squared">
</div>
<!-- Empty div but with a reserved 2:3 aspect-ratio area -->
<div class="wrapper portrait">
</div>
<!-- Empty div but with a reserved 3:2 aspect-ratio area -->
<div class="wrapper landscape">
</div>
</main>
<div>I Will Not CLS</div>
/* layout setting */
main {
padding: 2em;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
align-items: center;
grid-gap: 1em;
}
div {
background-color: #1CFFA6;
}
/* padding trick factorisation (scss function) */
@mixin aspect-ratio($width, $height) {
position: relative;
overflow: hidden;
&:before {
display: block;
content: "";
width: 100%;
padding-top: ($height / $width) * 100%;
}
}
/* our aspect-ratio box: let us call it our twic container */
.wrapper {
border: 10px solid red;
width: 100%;
}
/* setting a 1:1 aspect ratio */
.squared{
@include aspect-ratio(1, 1);
}
/* setting a 3:2 aspect ratio */
.landscape{
@include aspect-ratio(3, 2);
}
/* setting a 2:3 aspect ratio */
.portrait{
@include aspect-ratio(2, 3);
}
As the TwicPics script determines the transformations to be applied according to the context of the display area, all we have to do is place images in the reserved areas.
Here is a code pen where we have integrated the images in cover and contain mode.
Feel free to open the network console and see what happens when you scroll down.
Improving the user experience
We are almost done.
At this point, we load a perfectly sized and ideally compressed image. Aspect ratio is managed as well as the CLS optimization. The LQIP method reduces the perceived loading time, but the transition between preview and final image remains too rough.
This can be improved by taking advantage of TwicPics's life cycles.
Here are the few modifications to be made:
<!-- Blurry image is moved from img to the wrapper -->
<div
class="wrapper squared"
style="background-image:url('https://demo.twic.pics/cat_1x1.jpg.twic=v1/output=preview');"
>
<img
data-twic-src="image:cat_1x1.jpg"
>
</div>
...
<div
class="wrapper portrait contain"
style="background-image:url('https://demo.twic.pics/cat_1x1.jpg?twic=v1/contain=667x1000/output=preview');"
>
<img class="contain"
data-twic-src="image:cat_1x1.jpg"
>
</div>
.wrapper {
border: 10px solid red;
width: 100%;
background-size:cover;
background-repeat: no-repeat;
background-position: center;
overflow: hidden;
& img {
/** opacity set to 0 **/
opacity:0;
/** animation on opacity property **/
transition-property: opacity;
will-change: opacity;
transition-delay: 0s;
transition-duration: 400ms;
transition-timing-function: ease;
&.twic-done{
/** opacity set to 1 on image loaded event **/
opacity:1;
}
}
.contain {
object-fit: contain;
background-size: contain;
}
}
Here is a Codepen with complete integration.
Intermission
We can now display images that are perfectly sized for the display area. In addition, the CLS is optimized, lazy loading works, and we have implemented Low-Quality Image Placeholders (LQIP).
But it is far from perfect:
- the
HTML
andCSS
code is a bit heavy - some features would deserve to be more flexible (aspect ratio, display mode...)
- LQIP would require the addition of some javascript to be handled automatically.
A block of code remains to be done, including this HTML structure, CSS code, and the necessary javascript. In short, a Web Component.
Part Two - TwicPics Components to the rescue
The examples below use the React
version of TwicPics Components. They can easily be adapted to other available versions.
Configuration
In the first part of this post, to use the TwicPics SaaS, we loaded the script by adding:
<!-- Installation of TwicPics -->
<script src="https://demo.twic.pics/?v1" async defer></script>
The equivalent for TwicPics Components is the following code:
/* index.js (or jsx) */
/* imports TwicPics Components */
import { installTwicPics } from "@twicpics/components/react";
import "@twicpics/components/style.css";
/* configuration */
installTwicPics({
domain: `https://demo.twic.pics`
});
Our TwicPics Components are now ready to be used, and we can move on to the next step.
Basic Usage
To reproduce the previous example, all we have to do is:
/* your-template.js (or jsx) */
/* imports the component */
import { TwicImg } from "@twicpics/components/react";
...
<!-- Uses the component -->
const TwicSample = () => {
return (
<main>
<TwicImg src="cat_1x1.jpg" />
<TwicImg src="cat_1x1.jpg" ratio="2/3" />
<TwicImg src="cat_1x1.jpg" ratio="3/2" />
<TwicImg src="cat_1x1.jpg" mode="contain" />
<TwicImg src="cat_1x1.jpg" ratio="2/3" mode="contain" />
<TwicImg src="cat_1x1.jpg" ratio="3/2" mode="contain" />
</main>
);
};
export default TwicSample;
/* The css of the components is embedded in the installation */
/* Nothing to add here */
See how easy it is to manage the aspect-ratio and the filling option with ratio and mode properties.
Here is a Codesandbox with the complete example.
Everything is in place: CLS optimization, LQIP, Lazy Loading, respect of aspect-ratio, and fill option.
NB: in the contain
mode, the aspect ratio is applied to the display area, not the image itself.
Templating driven components
The template controls the behavior of TwicPics Components through the properties they expose.
We can see these properties working in the following online examples: ratio, mode, focus, placeholder, position, transition.
As an example, we can adapt the transition time of the LQIP by setting the transitionDuration
attribute. We can even disable LQIP while keeping the CLS optimization by setting placeholder
to none
.
/* your-template.js (or jsx) */
/* imports the component */
import { TwicImg } from "@twicpics/components/react";
...
<!-- Uses the component -->
const TwicSample = () => {
return (
<main>
/* Default behavior */
<TwicImg src="cat_1x1.jpg" />
/* No placeholder but CLS still optimized */
<TwicImg src="cat_1x1.jpg" placeholder="none"/>
/* A long transition */
<TwicImg src="cat_1x1.jpg" transitionDuration="10000ms" />
</main>
);
};
export default TwicSample;
A full description of those properties is available here.
Style-driven components
TwicPics Components can also be set up by using pure CSS through the power of CSS variables.
The list of usable variables is available here.
/** <your-style>.css **/
.landscape {
/** applies a 2/3 aspect ratio for all landscape class elements **/
--twic-ratio: calc(2/3);
}
.portrait {
/** applies a 3/2 aspect ratio for all portrait class elements **/
--twic-ratio: calc(3/2);
}
.square{
/** applies a 1/1 aspect ratio for all square class elements **/
--twic-ratio: calc(1);
}
.contain {
/** applies a contain fill option for all contain class elements **/
--twic-mode: contain;
}
/* your-template.js (or jsx) */
/* imports the component */
import { TwicImg } from "@twicpics/components/react";
...
<!-- Uses the component -->
const TwicSample = () => {
return (
<main className="contain portrait">
<TwicImg src="cat_1x1.jpg" className="square" />
<TwicImg src="cat_1x1.jpg" />
<TwicImg src="cat_1x1.jpg" className="landscape" />
</main>
);
};
export default TwicSample;
Here is a Codesandbox that demonstrates the style-driven approach.
Feel free to check the features demo.
What about Art Direction?
We saved the best example for last.
Since the advent of responsive design, art direction has always been the worst problem to deal with.
Adapting the image size to the device is no longer a problem. But what about its aspect ratio?
There are several approaches to solve the problem.
For example, one could imagine this solution using TailwindCSS and TwicPics Components
/** Dealing with four element in our template **/
const ArtDirectionSample = () => {
<div className="sm:hidden">
<TwicImg src="cat_1x1.jpg" ratio="3/4"/>
</div>
<div className="hidden md:block lg:hidden">
<TwicImg src="cat_1x1.jpg" ratio="1"/>
</div>
<div className="hidden lg:block xl:hidden">
<TwicImg src="cat_1x1.jpg" ratio="4/3"/>
</div>
<div className="hidden xl:block">
<TwicImg src="cat_1x1.jpg" ratio="16/9"/>
</div>
};
export default ArtDirectionSample;
With TwicPics Components and the style-driven approach, the solution becomes so simple.
First, we define some CSS
rules:
/** <your-style>.scss **/
/** Breakpoint definition **/
$breakpoint-md: "only screen and (min-width: 768px)";
$breakpoint-lg: "only screen and (min-width: 1024px)";
$breakpoint-xl: "only screen and (min-width: 1280px)";
/** mobile first **/
.style-driven-responsive {
--twic-ratio: calc(3/4);
}
/** aspect ratio definition vs breakpoints**/
@media #{ $breakpoint-md } {
.style-driven-responsive {
--twic-ratio: calc(1);
}
}
@media #{ $breakpoint-lg } {
.style-driven-responsive {
--twic-ratio: calc(4/3);
}
}
@media #{ $breakpoint-xl } {
.style-driven-responsive {
--twic-ratio: calc(16/9);
}
}
Then, we write our template with a single component that will follow our CSS
directives.
/* your-template.js (or jsx) */
/* imports the component */
import { TwicImg } from "@twicpics/components/react";
...
const TwicSample = () => {
/** Only one element in our template **/
return (
<div className="style-driven-responsive">
<TwicImg src="cat_1x1.jpg" />
</div>
);
};
export default TwicSample;
Here is a Codesandbox to play with.
Feel free to check the Art Direction demo.
Conclusion
It is possible to simply and efficiently deliver images and short videos on a website. As simple as writing:
<img src="path/to/image" />
but with the condition of writing:
<TwicImg src="path/to/image" />
TwicPics Components are free and open-source. Don't hesitate to test them for yourself.
The only requirement to use them is to have a TwicPics account. If you don't already have one, you can easily create your own TwicPics account for free.
In a future article, we'll go into more detail about integrating the components into Next.js
and Gatsby
.
In the meantime, feel free to explore our demonstrations for Angular, React, Next.js, Gatsby.js and Vue.js (version2).
Stay tuned :-)
Top comments (0)