Pure CSS dynamic multiline paragraph placeholder

Whoa! Long post title for a long story about:

  • how to get a container height of exact n line-heights (tldr; use display:grid with grid-template-rows)
  • how to make use of it by creating a cool dynamic paragraph placeholder with quite elegant CSS an HTML

How did this start?

I was assigned to a task that required placeholders for image and number of lines of text below.

The number of lines can vary but is constant for each of the code execution.

To make it as easy as possible, it’s a no-brainer. Just a few lines of js which would create fake lines with dummy content, make it transparent and put some gray background. Done.

Since I don’t like doing things in js if I know there might be a pure CSS way, I started to think if it’s even possible.

Thanks to new CSS standards which are quite powerful, it is. Here’s the short explanation.

Line-height unit/value? Doesn’t exists

First must-have for the placeholder is to make the empty container to be the exact same height as filled one, so we avoid jumping of the UI which is never a good idea.

To meet this requirement, we need to know how many lines of text we expect.

For this scenario, we assume that’s a given value. For most use cases, like product cards, excerpts, user infoboxes, it’s usually defined by the design.

For others, it would be still given, since for a long text we would rather want to show just two or three lines of placeholder. For those cases, the content is usually in some main container, so jumping would be avoided by its nature.

But how do I make the container height exactly the same as it would have real content?

There are two simple ways of sizing the container which yet doesn’t have the real content.

Render a dummy, transparent content

By rendering a dummy, transparent content you have 100% guarantee that the browser would render it in the same way as it would be a real content.

A problem with that approach is that the content might be accessed by the screen readers (aria-hidden is the solution). But, additionally, you need to build the HTML and render it, then destroy this DOM node and create a new one when the real content is there. Not the biggest performance issue, but also not something you really want to do.

Leave the container empty, render the same size as before pseudo-element

You can also leave the container empty and render exact same and empty container as a  before pseudo-element.

First intuitive thought would be to use some relative units like em, or ex.

This approach won’t work since there is no such unit in CSS. All the relative units are either related to:

  • container (%)
  • viewport (vh, vw)
  • or font (em, rem, ex, ch…)

There is no unit that would strictly relate to line-height, like 1lh, or 1cap.

But there is a trick that covers this particular case: grid fraction.

1 row fraction happens to have a default height same a line-height

When defining a grid you can tell how many grid rows you want to “prepare” and those are rendered even if there is no related content.

So, usual HTML grid definition looks like this:

<style>
    .grid {
        display: grid;
        grid-template-rows: repeat(2, 1fr);
    }
</style>
<div class="grid">
    <div class="row"></div>
    <div class="row"></div>
</div>

To prepare an empty container that has the same size as it would contain three lines of text the HTML looks like this:

<style>
    .content.content--placeholder:before {
        content: "\a0";
        display: grid;
        grid-template-rows: repeat(var(--expected-lines), 1fr);
    }
</style>
<div class="content content--placeholder" style="--expected-lines: 3"></div>

The browser renders an empty container, with a :before pseudo-element. The pseudo-element renders one line of text, a non breaking space which is invisible (both for visual and screen readers) but makes the line actually render.

Additionally, the content is a grid with a number of rows which is passed as a CSS variable given from the HTML style attribute.

With only this code, the browser renders the container with the exact same size as it would render three lines of real text.

Time for a CSS only placeholder

Rendering a placeholder in CSS at this point is just a pure entertainment. By having a possibility of rendering gradients it’s quite easy to generate a few grey lines.

<style>
    .content.content--placeholder {
        content: "\a0";
        display: grid;
        grid-template-rows: repeat(var(--expected-lines), 1fr);
        background: linear-gradient(to bottom, rgba(197,197,197,0) 0%,rgba(197,197,197,0) 18%,rgba(197,197,197,0) 19%,rgba(197,197,197,0.5) 20%,rgba(197,197,197,0.5) 50%,rgba(197,197,197,0.5) 80%,rgba(197,197,197,0.5) 81%,rgba(197,197,197,0) 82%,rgba(197,197,197,0) 100%) repeat;
        background-size: 100% calc(100% / var(--expected-lines))
    }
</style>
<div class="content content--placeholder" style="--expected-lines: 3"></div>

The background is a linear gradient created with ColorZilla generator. The size of the background is 100% wide and height is a result of dividing 100% by the number of lines we want to render.

Result

Now, the only job for javascript is to define how many lines the placeholder should show by manipulating the CSS variable. Then, when the content is ready to be shown, to render the content and remove the class which triggers the :before element.

You can find a working demo on the GitHub page (source code).

CSS image transition and memory leaks

CSS transitions are features we all love and admire. They allow us to communicate with browser rendering engine and make cool things we were never able to do so fast and easy.

We also assume that we just have to use CSS as an API and don’t really need to care about memory management. Everything should be handled by the browser, we just say what we want from the engine.

However, there are some things we should always have in mind to make the UI smooth and also nice to watch.

One of the “things” is the magic translate3d(0,0,0) hack which forced the browser to use GPU (if available) for rendering the element with this property applied.

It is also possible to make everything completely wrong if we don’t know what is really happening under the hood (or how specific engine handle specific transitions).

IMG causes memory leak

For a regular job I work with webviews. We are focused o creating cool e-commerce apps for merchants around the world. Recently we experiment with single view approach hybrid apps. Results are pretty good, we noticed huge speed improvement, however developing an app in single view approach also opened our eyes on things we didn’t consider before.

One of the issues was memory management.

Single view application loads every page in one single webview (or browser tab if it runs inside browser). It means there’s need to really take care about memory leaks. It’s more important to manage event listeners, object destructions and rendering process. What is flushed from memory automatically in multiview app when page is removed from stack, may stay forever in single view one.

After some tests we found out that app takes far too much memory after some time of usage and is eventually killed by jetsam (ios memory watchdog). We didn’t have huge DOM (we did test it though and memory management works awesome for 600 tested Framework7 pages in DOM).

The reason behind this was jpeg rendering layers which were never flushed.

It seems like in iOS 9, when opacity is applied directly to an image, another GPU layer is created to handle transitions, which is also never released. Even if the image is removed from DOM.

Fix for this issue is pretty simple (apply opacity transitions to image parent, not image itself), however research on this took some hours.

Lesson learned. Do not apply opacity to an image itself.