Cms: SVG images have no width and height parameters which causes layout shifts

Created on 18 Nov 2020  路  5Comments  路  Source: statamic/cms

Bug Description

How to get the width and height of a SVG image?
Google Lighthouse complains:
Image elements do not have explicit width and height
Set an explicit width and height on image elements to reduce layout shifts and improve CLS. Learn more
https://web.dev/optimize-cls/?utm_source=lighthouse&utm_medium=devtools#images-without-dimensions

How do I set (or get) width and height for my svg images?
Here is the code that I use:

{{ image }}
<img src="{{ url }}" width="" height="" class="mx-auto lazyload">
 {{ /image }}

I know there is a special tag for svg's to inline them, but this is not what I need, and even counterproductive because of the complexity of the SVG image.
And I can not set the height and width to just some values, because then the reserved space has not the correct aspect ratio or size.
All my SVG's have a viewBox attribute, so it should be easy to get the required dimensions from this attribute.

Is there a way to do this with Statamic?
I think the dimensions and/or the aspect ratio should be saved in the meta information of the SVG asset when you upload it, so it can be used with an image tag.

How to Reproduce

Upload a SVG to your assets folder and output it in a template

Extra Detail

Video of the problem
https://www.youtube.com/embed/VDhIxPUoQ8Y

Environment

Statamic 3.0.28 Pro
Laravel 8.15.0
PHP 7.4.12
cnj/seotamic 2.0.2
octoper/statamic-inline-assets 4.0.2
rias/statamic-markdown-highlight 1.0.4
spatie/statamic-responsive-images 1.4.0
statamic/ssg 0.2.0

assets bug

Most helpful comment

I've built out a tag that should help you work around the issue until it's properly supported in Statamic.

Create App\Tags\SvgViewbox.php, with the following contents:

<?php

namespace App\Tags;

use Statamic\Tags\Tags;

class SvgViewbox extends Tags
{
    protected $regexPattern = "#viewbox=[\"']\d* \d* (\d*+(\.?+\d*)) (\d*+(\.?+\d*))#i";

    // {{ svg_viewbox:width :url="image_url" }}
    public function width()
    {
        $url = $this->params->get('url');

        if (! str_contains($url, 'http')) {
            $url = config('app.url') . '/' . $url;
        }

        $svg = file_get_contents($url);

        preg_match($this->regexPattern, $svg, $viewBox);

        return (float) $viewBox[1];
    }

    // {{ svg_viewbox:height :url="image_url" }}
    public function height()
    {
        $url = $this->params->get('url');

        if (! str_contains($url, 'http')) {
            $url = config('app.url') . '/' . $url;
        }

        $svg = file_get_contents($url);

        preg_match($this->regexPattern, $svg, $viewBox);

        return (float) $viewBox[3];
    }
}

Then in your Antlers templates, you can reference it like this:

width {{ svg_viewbox:width url="/assets/site/scratches.svg" }}
height {{ svg_viewbox:height url="/assets/site/scratches.svg" }}

Just pass in the asset's URL as the url parameter. Hopefully it should work for you.

All 5 comments

Push!?

Pull!?

Until we're able to implement something, you could consider writing a tag that would read the viewBox from the svg.

I've built out a tag that should help you work around the issue until it's properly supported in Statamic.

Create App\Tags\SvgViewbox.php, with the following contents:

<?php

namespace App\Tags;

use Statamic\Tags\Tags;

class SvgViewbox extends Tags
{
    protected $regexPattern = "#viewbox=[\"']\d* \d* (\d*+(\.?+\d*)) (\d*+(\.?+\d*))#i";

    // {{ svg_viewbox:width :url="image_url" }}
    public function width()
    {
        $url = $this->params->get('url');

        if (! str_contains($url, 'http')) {
            $url = config('app.url') . '/' . $url;
        }

        $svg = file_get_contents($url);

        preg_match($this->regexPattern, $svg, $viewBox);

        return (float) $viewBox[1];
    }

    // {{ svg_viewbox:height :url="image_url" }}
    public function height()
    {
        $url = $this->params->get('url');

        if (! str_contains($url, 'http')) {
            $url = config('app.url') . '/' . $url;
        }

        $svg = file_get_contents($url);

        preg_match($this->regexPattern, $svg, $viewBox);

        return (float) $viewBox[3];
    }
}

Then in your Antlers templates, you can reference it like this:

width {{ svg_viewbox:width url="/assets/site/scratches.svg" }}
height {{ svg_viewbox:height url="/assets/site/scratches.svg" }}

Just pass in the asset's URL as the url parameter. Hopefully it should work for you.

Thank you @damcclean. It works now. It would be great if this was a core feature, without requiring a special tag. Cheers

This is now available in 3.1 beta.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jelleroorda picture jelleroorda  路  3Comments

filipac picture filipac  路  4Comments

sandervanh picture sandervanh  路  4Comments

austenc picture austenc  路  3Comments

jimblue picture jimblue  路  3Comments