Introducing TwicPics Components
Tags: webperf, ux, image
Published: 2022-08-22 08:56:35

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](https://www.npmjs.com/package/@twicpics/components).

## Introduction

[TwicPics](https://www.twicpics.com/) 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](https://www.npmjs.com/package/@twicpics/components) handles.

[TwicPics Components](https://www.npmjs.com/package/@twicpics/components) is a **free and open-source web component collection** that replaces `img` and `video` tags.

```html
  <!-- a classical img usage -->
  <img src="path/to/image" />
```

```html
  <!-- a TwicPics img component usage -->
  <TwicImg src="path/to/image" />
```

Available for the most popular frameworks ([Angular](https://github.com/TwicPics/components/blob/main/documentation/angular.md), [React](https://github.com/TwicPics/components/blob/main/documentation/react.md), [Gatsby](https://github.com/TwicPics/components/blob/main/documentation/gatsby.md), [Next.js](https://github.com/TwicPics/components/blob/main/documentation/next.md), [Svelte](https://github.com/TwicPics/components/blob/main/documentation/svelte3.md),[Vue.js (version 2)](https://github.com/TwicPics/components/blob/main/documentation/vue2.md), [Vue.js (version 3)](https://github.com/TwicPics/components/blob/main/documentation/vue3.md), [Nuxt.js](https://github.com/TwicPics/components/blob/main/documentation/nuxt2.md), and [native Web Components](https://github.com/TwicPics/components/blob/main/documentation/webComponents.md)), they allow you to integrate the [TwicPics](https://www.twicpics.com/) 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](https://account.twicpics.com/signup).

**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](https://www.twicpics.com/docs/guides/best-practices#lazy-load-off-screen-media), [CLS optimization](https://www.twicpics.com/blog/images-and-core-web-vitals#images-and-cumulative-layout-shift), [respect of aspect-ratio](https://www.twicpics.com/docs/guides/placeholders#responsive-placeholders), [filling option](https://www.twicpics.com/docs/api/transformations#cover), [LQIP (Low-Quality Image Placeholders)](https://www.twicpics.com/docs/guides/best-practices#lqip--blurry-image-placeholder).

**In the second part**, we will see how easy it is to do the same thing (and even more) by using [TwicPics Components](https://www.npmjs.com/package/@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](https://www.twicpics.com/docs/script/setup) and the `data-twic-src` property instead of the native `src` attribute.

```html
<!-- 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](https://www.twicpics.com/docs/guides/best-practices#lqip--blurry-image-placeholder).

We tell the browser to load a lightweight blurry placeholder first by using the [output=preview](https://www.twicpics.com/docs/api/transformations#output) API transformation and to replace it with the pixel-perfect version later.

```html
<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**.

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

```css
img {
  display:block;
  width:100%;
  height:100%;
}
```

Let's test it in the example below.

```html
<!-- 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:

| ![Layout Driven - First attempt](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qjecvj9j2ypnhts9jxwi.jpg) | 
|:--:| 
| <center>*Layout Driven - First attempt*</center> |


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.

```css
img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.contain{
  object-fit: contain;
}
```

Let's test our modifications with this new example:

```html
<!-- 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>
```

| ![Layout Driven - Second attempt](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nihrx4btmq5m482uy8e5.jpg) | 
|:--:| 
| <center>*Layout Driven - Second attempt*</center> |


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.

{% codepen https://codepen.io/twicpics/pen/JjMLpPv %}

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](https://css-tricks.com/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](https://www.twicpics.com/blog/images-and-core-web-vitals#images-and-cumulative-layout-shift). Of course, you could also use the [`aspect-ratio` CSS property](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio) if you target modern browsers only.

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

```css
/* 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);
}
```

| ![Aspect Ratio Boxes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ouj1s73cnujtij1ntrc.jpg) | 
|:--:| 
| <center>*Aspect Ratio Boxes*</center> |

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.

{% codepen https://codepen.io/twicpics/pen/PoEReKX %}


### 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](https://www.twicpics.com/docs/script/dynamic#life-cycles).

Here are the few modifications to be made:

```html
<!-- 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>
```

```css
.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.

{% codepen https://codepen.io/twicpics/pen/oNpoXBx %}

## 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` and `CSS` 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](https://developer.mozilla.org/en-US/docs/Web/Web_Components).

## Part Two - TwicPics Components to the rescue

The examples below use the `React` version of [TwicPics Components](https://www.npmjs.com/package/@twicpics/components). They can easily be adapted to other [available versions](https://github.com/TwicPics/components/tree/main/documentation#supported-frameworks).

### Configuration

In the first part of this post, to use the TwicPics SaaS, we loaded the script by adding:

```html
<!-- Installation of TwicPics -->
<script src="https://demo.twic.pics/?v1" async defer></script>
```

The equivalent for [TwicPics Components](https://www.npmjs.com/package/@twicpics/components) is the following code:

```js
/* 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](https://www.npmjs.com/package/@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:

```js
/* 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;
```

```scss
/* 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](https://twicpics-react-demo.netlify.app/ratio) and [mode](https://twicpics-react-demo.netlify.app/mode) properties.

Here is a Codesandbox with the complete example.

{% codesandbox 5pdwrq %}

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](https://www.npmjs.com/package/@twicpics/components) through the properties they expose.

We can see these properties working in the following online examples: [ratio](https://twicpics-react-demo.netlify.app/ratio), [mode](https://twicpics-react-demo.netlify.app/mode), [focus](https://twicpics-react-demo.netlify.app/focus), [placeholder](https://twicpics-react-demo.netlify.app/placeholders), [position](https://twicpics-react-demo.netlify.app/position), [transition](https://twicpics-react-demo.netlify.app/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`.

```js
/* 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](https://github.com/TwicPics/components/blob/main/documentation/react.md#components-properties).

### Style-driven components

[TwicPics Components](https://www.npmjs.com/package/@twicpics/components) can also be set up by using pure CSS through the power of [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties).

The list of usable variables is available [here](https://github.com/TwicPics/components/blob/main/documentation/react.md#css-variables).

```css
/** <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;
}

```

```js
/* 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.

{% codesandbox m1zxqs %}

Feel free to check the [features demo](https://twicpics-react-demo.netlify.app/style-driven).

### What about Art Direction?

We saved the best example for last.

Since the advent of [responsive design](http://usecases.responsiveimages.org/), **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](https://tailwindcss.com/) and [TwicPics Components](https://www.npmjs.com/package/@twicpics/components)

```js
/** 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](https://www.npmjs.com/package/@twicpics/components) and the style-driven approach, the solution becomes so simple.

First, we define some `CSS` rules:

```scss
/** <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.

```js
/* 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.

{% codesandbox d6hd2g %}

Feel free to check the [Art Direction demo](https://twicpics-react-demo.netlify.app/art-directions).

## Conclusion

It is possible to simply and efficiently deliver images and short videos on a website. As simple as writing:

```html
  <img src="path/to/image" />
```

but with the condition of writing:

```html
  <TwicImg src="path/to/image" />
```

[TwicPics Components](https://www.npmjs.com/package/@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](https://account.twicpics.com/signup).

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](https://twicpics-angular-demo.netlify.app/), [React](https://twicpics-react-demo.netlify.app/), [Next.js](https://twicpics-next-demo.netlify.app/), [Gatsby.js](https://twicpics-gatsby-demo.netlify.app/) and [Vue.js (version2)](https://twicpics-vue-demo.netlify.app).

Stay tuned :-)