feat(hero): responsive srcset for carousel slides
Phones were downloading the full 1.5 MB / 2 MB hero JPEG. Now the image macro emits a `srcset` ladder (480/768/1200/1600/2560/3840/5345 w) with `sizes="100vw"`, so a 480 px-wide phone picks the ~19 KB variant and 5K screens still get the native resolution. Also drop `loading="lazy"` on the first slide — it is the LCP element and should load eager. Macro changes: - Add `srcset_widths` (CSV string, since Tera macros only allow scalar defaults) and `sizes` parameters. - Add `lazy=true` parameter; callers can pass `lazy=false` for above-the-fold images. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+12
-5
@@ -16,11 +16,18 @@
|
|||||||
<div class="carousel-inner" role="listbox">
|
<div class="carousel-inner" role="listbox">
|
||||||
{% for slide in section.extra.slides %}
|
{% for slide in section.extra.slides %}
|
||||||
<div class="item{% if loop.first %} active{% endif %}">
|
<div class="item{% if loop.first %} active{% endif %}">
|
||||||
{# Serve the source image at full resolution — Bootstrap-3 only
|
{# Bootstrap-3 only constrains hero images via `max-width: 100%`,
|
||||||
constrains via `max-width: 100%`, so on viewports wider than
|
so the natural image width has to cover the widest viewport
|
||||||
the natural image width the slide would otherwise sit short
|
we care about. We hand the browser a `srcset` ladder up to
|
||||||
of the column edge. Live does the same (no resize). #}
|
5345 px (the source's native width) and let it pick — phones
|
||||||
{{ image::render(src=slide.image, alt=slide.alt) }}
|
grab ~480-800 px, desktops 1600-2560 px, 5K screens the full
|
||||||
|
file. The first slide is the LCP element, so it loads eager. #}
|
||||||
|
{{ image::render(
|
||||||
|
src=slide.image,
|
||||||
|
alt=slide.alt,
|
||||||
|
srcset_widths="480,768,1200,1600,2560,3840,5345",
|
||||||
|
sizes="100vw",
|
||||||
|
lazy=not loop.first) }}
|
||||||
<div class="carousel-caption">
|
<div class="carousel-caption">
|
||||||
<p>{{ slide.heading }}</p>
|
<p>{{ slide.heading }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+27
-12
@@ -1,22 +1,37 @@
|
|||||||
{# Image-rendering macro, used both by templates (directly via import)
|
{# Image-rendering macro, used both by templates (directly via import)
|
||||||
and indirectly by the `img` shortcode (which forwards to it).
|
and indirectly by the `img` shortcode (which forwards to it).
|
||||||
|
|
||||||
With `width > 0`: resizes via Zola's image pipeline (file size benefit)
|
Modes:
|
||||||
but emits no width/height attributes — the theme CSS sizes the image,
|
- `srcset_widths="480,768,…"` non-empty: emit a responsive `srcset`
|
||||||
matching the live Grav site's pattern. Avoiding the HTML width attribute
|
over those widths. `sizes` should describe the layout (e.g.
|
||||||
sidesteps a stretch artefact triggered by Bootstrap-3's
|
"100vw" for the full-width hero). The `src` fallback uses the
|
||||||
`img { height: auto !important }` interacting with `.float-right`
|
largest width. CSV form because Tera macros only allow scalar
|
||||||
padding.
|
defaults, not arrays.
|
||||||
|
- `width > 0`: single resized variant via Zola's image pipeline.
|
||||||
|
- `width = 0` (default): raw <img> straight from /static/images/.
|
||||||
|
|
||||||
With `width = 0` (default): emits the raw <img> straight from
|
In all modes we omit the HTML width/height attributes — the theme CSS
|
||||||
/static/images/ — same shape, no resize. #}
|
sizes the image, matching the live Grav site's pattern. Avoiding the
|
||||||
|
HTML width attribute sidesteps a stretch artefact triggered by
|
||||||
|
Bootstrap-3's `img { height: auto !important }` interacting with
|
||||||
|
`.float-right` padding.
|
||||||
|
|
||||||
{% macro render(src, width=0, alt="", class="") %}
|
`lazy=false` switches off `loading="lazy"`. Use it for above-the-fold
|
||||||
|
images (LCP candidates) like the first hero slide. #}
|
||||||
|
|
||||||
|
{% macro render(src, width=0, alt="", class="", srcset_widths="", sizes="", lazy=true) %}
|
||||||
{%- set width = width | int -%}
|
{%- set width = width | int -%}
|
||||||
{%- if width > 0 -%}
|
{%- set loading_attr = "" -%}
|
||||||
|
{%- if lazy -%}{%- set loading_attr = ' loading="lazy"' -%}{%- endif -%}
|
||||||
|
{%- if srcset_widths -%}
|
||||||
|
{%- set widths = srcset_widths | split(pat=",") -%}
|
||||||
|
{%- set largest = widths | last | trim | int -%}
|
||||||
|
{%- set fallback = resize_image(path="static/images/" ~ src, width=largest, op="fit_width", quality=82) -%}
|
||||||
|
<img src="{{ fallback.url | safe }}" srcset="{%- for w_str in widths -%}{%- set w = w_str | trim | int -%}{%- set r = resize_image(path="static/images/" ~ src, width=w, op="fit_width", quality=82) -%}{{ r.url | safe }} {{ w }}w{%- if not loop.last -%}, {%- endif -%}{%- endfor -%}" sizes="{{ sizes }}" alt="{{ alt }}"{% if class %} class="{{ class }}"{% endif %}{{ loading_attr | safe }}>
|
||||||
|
{%- elif width > 0 -%}
|
||||||
{%- set resized = resize_image(path="static/images/" ~ src, width=width, op="fit_width", quality=85) -%}
|
{%- set resized = resize_image(path="static/images/" ~ src, width=width, op="fit_width", quality=85) -%}
|
||||||
<img src="{{ resized.url | safe }}" alt="{{ alt }}"{% if class %} class="{{ class }}"{% endif %} loading="lazy">
|
<img src="{{ resized.url | safe }}" alt="{{ alt }}"{% if class %} class="{{ class }}"{% endif %}{{ loading_attr | safe }}>
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
<img src="/images/{{ src | safe }}" alt="{{ alt }}"{% if class %} class="{{ class }}"{% endif %} loading="lazy">
|
<img src="/images/{{ src | safe }}" alt="{{ alt }}"{% if class %} class="{{ class }}"{% endif %}{{ loading_attr | safe }}>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{% endmacro render %}
|
{% endmacro render %}
|
||||||
|
|||||||
Reference in New Issue
Block a user