Skip to content
Grav 2.0 is officially stable. Read the announcement →

Community guidelines

Please keep discussions civil and on-topic. Repeated violations may lead to a temporary ban.

Themes & Styling

Set default attributes to markdown images to avoid cls

theme

Started by Christiana 5 years ago · 11 replies · 1092 views
5 years ago

Hi!

To avoid negative Cumulative Layout Shift (CLS) ranking I need to set default width and height attributes to all images that an author writes in markdown content field.

Is there a possibilty to set them according to objects original dimensions? I need help to find out how to modify the output of the image.

In the same way I would like to set default classes to use own javascript like lazyload.

(https://web.dev/optimize-cls/?utm_source=lighthouse&utm_medium=devtools)

I am looking forward to an answer!

Stay healthy!
Christiana!

5 years ago

@christiana83, You could try the following:

Using a fresh install of Grav 1.7.5

  • In Twig template:

    TWIG
    // Twig
    
    {% set image = page.media.images|first %}
    {{ image.height().width().html('Image inserted in Twig', 'Alt', 'Class') | raw }}
    
    TXT
    // Generated HTML
    
    <img height="800" width="1200" title="Image inserted in Twig" 
    alt="Alt" class="Class" src="/user/pages/03.cls/image.jpg">
    
  • Using a custom shortcode (see https://github.com/getgrav/grav-plugin-shortcode-core#custom-shortcodes)

    • Install plugin ‘Shortcode Core’: $ bin/gpm install shortcode-core
    • Create file ‘user/config/plugins/shortcode-core.yaml’ and add:
    TXT
    custom_shortcodes: '/user/custom/shortcodes'
    
    • Create file ‘user/custom/shortcodes/ClsShortcode.php’ with the following content:
    PHP
    <?php
    namespace Grav\Plugin\Shortcodes;
    
    use Thunder\Shortcode\Shortcode\ShortcodeInterface;
    
    class ClsShortcode extends Shortcode
    {
       public function init()
       {
          $this->shortcode->getRawHandlers()->add('image', function(ShortcodeInterface $sc) {
             $image = $sc->getParameters()['src'];
             $title = $sc->getParameters()['title'];
             $alt = $sc->getParameters()['alt'];
             $classes = $sc->getParameters()['classes'];
    
             return "{{ page.media['$image'].width().height().html('$title', '$alt', '$classes') | raw }}";
          });
       }
    }
    
    • Create page ‘/user/pages/03.cls/default.md’ with the following content:
    YAML
    ---
    title: Cls
    process:
        twig: true
    ---
    
    [image src=image.jpg title="Image inserted in Markdown using Shortcode" alt=Alt classes="class1 class2"]
    
    • Add image ‘images.jpg’ to folder ‘/user/pages/03.cls’
    • Generated HTML
      TXT
      <img width="1200" height="800" 
      title="Image inserted in Markdown using Shortcode" 
      alt="Alt" class="class1 class2" 
      src="/user/pages/03.cls/image.jpg">
      
    • Off course, you could create a shortcode plugin to reuse it in multiple sites.
👍 1
5 years ago

Thanks for your help and a first approach!

Yes, I would have to generate this for all pages by default, including the approx. 1000 pages that already exist. Without the editors having to do it.

I thought of a possibility to generally change the output of the image markup by changing the translation of the markdown into html. Maybe via a hook in a plugin. I just haven't found out which one it is and whether there is an event for it.

5 years ago

@christiana83, My suggestions are not really of any help with the existing pages...

Take a look at event: onPageContentProcessed.

This event is fired after the page's content() method has processed the page content. This is particularly useful if you want to perform actions on the post-processed content but ensure the results are cached. Performance is not a problem because this event will not run on a cached page, only when the cache is cleared or a cache-clearing event occurs.

At the bottom of the Grav lifecycle it says:

Whenever a page has its content() method called, the following lifecycle occurs:

Page.php

  1. If content is NOT cached:
    1. Fire onPageContentRaw event
    2. Process the page according to Markdown and Twig settings. Fire onMarkdownInitialized event
    3. Fire onPageContentProcessed event
  2. Fire onPageContent event

During the event you will have to:

  • regex the content to find the image tag
  • get hold of the image to get its dimensions
  • update the content with dimensions
  • write content
👍 1
last edited 02/19/21 by pamtbaau
5 years ago

@christiana83, Did some playing with code. The following onPageContentProcessed event handler seems to work.

Note: Used images are in page's folder.

PHP
public function onPageContentProcessed($event) {
    $page = $event['page'];
    $media = $page->getMedia();
    $content = $page->getRawContent();

    // Get all <img> elements
    $matches = [];
    preg_match_all('#<img.*src="([^"]+)".*?/>#' , $content , $matches);

    $contentChanged = false;

    for($i = 0; $i < count($matches[0]); $i++) {
        // Skip <img> elements that already contains width and/or height
        if (preg_match('#(width|height)#', $matches[0][$i])) {
            continue;
        }

        $src = $matches[1][$i];
        $filename = substr($src, strrpos($src, '/') + 1);
        $img = $media->get($filename);

        $oldElement = substr($matches[0][$i], 0, -2);
        $newElement = $oldElement . 'width="' . $img['width'] . '" height="' . $img['height'] . '"';
        $content = str_replace($oldElement, $newElement, $content);

        $contentChanged = true;
    }

    if ($contentChanged) {
        $page->setRawContent($content);
    }
}

Page's Markdown:

TXT
![Image1](image1.jpg)
![Image2](image2.jpg)

Generated HTML:

HTML
<p>
<img alt="Image1" src="/user/pages/01.home/image1.jpg" width="1200" height="800"/>
<img alt="Image2" src="/user/pages/01.home/image2.jpg" width="639" height="481"/>
</p>

Alternatives:

<code>1.</code> Update Markdown by one-time run of plugin.
You could also loop through all pages in onPagesInitialized and update the Markdown. This should be a one-time action updating all pages once and for all.

YAML
// default.md

---
title: Home
body_classes: 'title-center title-h1h2'
---

![Image1](image1.jpg?classes=myclass)
![Image2](image2.jpg?resize=400,200)
PHP
// plugin onPagesInitialized event

public function onPagesInitialized($event) {
    $pages = $event['pages'];

    foreach($pages->all() as $page) {
        $raw = $page->raw();
        $media = $page->media();

        $matches = [];
        preg_match_all('#!\[[^\]]*\]\(([^?)]+)[^)]*\)#', $raw, $matches);

        for($i = 0; $i < count($matches[0]); $i++) {
            if (preg_match('/(height|width)/', $matches[0][$i])) {
                continue;
            }

            $src = $matches[1][$i];
            $img = $media[$src];

            $oldMarkdown = $matches[0][$i];
            $newMarkdown = str_replace('?', '?width=' . $img['width'] . '&height=' . $img['height'] . '&', $oldMarkdown);
            $raw = str_replace($oldMarkdown, $newMarkdown, $raw);

            $page->raw($raw);
            $page->save();
        }
    }
}
YAML
// Updated default.md

---
title: Home
body_classes: 'title-center title-h1h2'
---

![Image1](image1.jpg?width=639&height=481&classes=myclass)
![Image2](image2.jpg?width=1200&height=800&resize=400,200)

<code>2.</code> Admin onSave

You could of course do something similar when Admin saves a page.

👍 1
last edited 02/19/21 by pamtbaau
5 years ago

@pamtbaau in addition to say, I started including lazyloading for markdwon images with lazysizes.min.js (https://github.com/aFarkas/lazysizes)

So I choose the solution to use event hook onMarkdownInitialized($event) for manipulating the image output. This way I can also use to add the dimensions of the image later.

PHP
$page = $event['page']; 
$images = $page->media()->images();
$markdown->addInlineType('!', 'ImageExtended', 0);

$imageExtended = function($excerpt) use ($images) {

    $inlineImage = $this->inlineImage($excerpt);

    if (!isset($inlineImage)) {
        return null;
    };

    if (!isset($inlineImage['element']['attributes']['data-srcset'])){
        if (isset($inlineImage['element']['attributes']['srcset'])) $inlineImage['element']['attributes']['data-srcset'] = $inlineImage['element']['attributes']['srcset'];
        if (isset($inlineImage['element']['attributes']['src'])) $inlineImage['element']['attributes']['data-src'] = $inlineImage['element']['attributes']['src'];

        $inlineImage['element']['attributes']['class'] = isset($inlineImage['element']['attributes']['class']) ? $inlineImage['element']['attributes']['class'] . ' lazyload' : 'lazyload';

        unset($inlineImage['element']['attributes']['srcset']);              

        $inlineImage['element']['attributes']['src'] = '/user/themes/tecart-website-front/assets/img/preview-images/showcase.jpg';

    }
    return $inlineImage;
};

$markdown->inlineImageExtended = $imageExtended->bindTo($markdown, $markdown);

}

The javascript file is included in my base.html.twig

{% do assets.addJs('theme://assets/js/lazysizes/lazysizes.min.js', {'priority': 100, 'group': 'bottom'}) %}

The initial hint for my solution I found in a grav lazysizes plugin.

👍 1
5 years ago

@christiana83, I'll have to study your solution a bit more, but apart from that...

Just curious, what made you choose for an external lazyloading library while:

  • browsers now accept the loading="lazy|eager" attribute natively
  • Grav has added to function loading to set the attribute
5 years ago

@pamtbaau Yes, but native lazyloading by loading="lazy" is still not working in all browsers, e.g. safari.
Additionally the website ist that big and connected to other applications like attlassian jira and additionally with several static exports so that we havent't yet updated to the latets grav version. that issue is in progress.
And we have own image markups from twig files page modules with <picture> tags that need an lazyloading library.

last edited 02/24/21 by Christiana
5 years ago

@christiana83, Fair enough...

👍 1
5 years ago

@christiana83, I've created a PR with a proposal to add the height and width automatically when an <img> tag is created in Markdown.

👍 2
5 years ago

Above PR has been merged (albeit, without much of my initial code :-) ), which will allow height and width to be automatically added to images.

system.yaml has received a new option:

YAML
images:
  cls:                   # Cumulative Layout Shift: See https://web.dev/optimize-cls/
    auto_sizes: false    # Automatically add height/width to image
    aspect_ratio: false  # Reserve space with aspect ratio style
    retina_scale: 1      # scale to adjust auto-sizes for better handling of HiDPI resolutions

In markdown, you can use:

TXT
![](sample-image.jpg)

or override settings like:

TXT
![](sample-image.jpg?autoSizes=true)

Which will generate:

TXT
<img alt="" 
  src="/path/to/sample-image.jpg" 
  width="1024" height="768" />
👍 2

Suggested topics

Topic Participants Replies Views Activity
Themes & Styling · by Pedro M, 2 months ago
4 193 2 months ago
Themes & Styling · by Ian, 2 months ago
3 90 2 months ago
Themes & Styling · by Norbert, 2 years ago
11 449 3 months ago
Themes & Styling · by Lukáš Findeis, 3 months ago
0 44 3 months ago
Themes & Styling · by Sebadamus, 4 months ago
5 123 3 months ago