Skip to content

Instantly share code, notes, and snippets.

@Accudio
Last active February 19, 2023 15:42
Show Gist options
  • Save Accudio/00010ee93e1a78d2dfed0bd86a45afbc to your computer and use it in GitHub Desktop.
Save Accudio/00010ee93e1a78d2dfed0bd86a45afbc to your computer and use it in GitHub Desktop.
Image macros and functions for Imgix on Daysmart
{# using macro directly #}
{{ image({
url: '/path/to/image.jpg',
alt: 'Image alt attribute',
class: 'mb-30',
imgClass: 'rounded',
lazy: false,
width: 1920,
height: 1080,
ratio: 0.5,
srcset: {
sizes: '100vw',
ldpiMin: 700,
ldpiMax: 837,
hdpiMin: 300,
hdpiMax: 700
}
}) }}
{# using ACF image array and Timber #}
{{ acfImage({
image: post.meta('photo'),
class: 'mb-30',
imgClass: 'rounded',
ratio: 0.5,
srcset: {
sizes: '100vw',
ldpiMin: 700,
ldpiMax: 837,
hdpiMin: 300,
hdpiMax: 700
}
}) }}
{# ==============================================
# Image Macros
# ============================================== #}
{##
# main image macro with full arguments
#
# arguments:
{
// required
url, // URL to original image, either relative or with Imgix subdomain
width, // width of original image in pixels
height, // height of original image in pixels
// optional
alt, // alt attribute. Defaults to empty string
class, // classes to apply to <picture> element
imgClass, // classes to apply to <img> element
lazy, // should this image be lazyloaded. Defaults to true
ratio, // force a specific aspect ratio. In decimal format found by height / width.
srcset: { // see https://jakearchibald.com/2021/serving-sharp-images-to-high-density-screens/ for info about ldpi/hdpi
sizes, // sizes attribute, if not set browsers assume 100vw
ldpiMin, // the minimum image width to generate for srcset at 1x dpi screens
ldpiMax, // the maximum image width to generate for srcset at 1x dpi screens
hdpiMin, // the minimum image width to generate for srcset at >1.5x dpi screens
hdpiMax // the maximum image width to generate for srcset at >1.5x dpi screens
},
type, // if set to svg+xml SVG won't be transformed
params // object of parameters to provide to imgix to add to URL. See https://docs.imgix.com/apis/rendering for options
}
#}
{% macro image(args) %}
{% set opts = {
url: args.url,
alt: ( args.alt ?? '' ),
class: ( args.class ?? '' ),
imgClass: ( args.imgClass ?? '' ),
lazy: ( args.lazy ?? true ),
width: args.width,
sizes: args.sizes,
srcset: ( args.srcset ?? false ),
params: ( args.params ?? false ),
type: ( args.type ?? 'image' )
} %}
{% set height = args.height %}
{# if image is an SVG #}
{% if opts.type == 'svg+xml' %}
<div class="image{% if opts.class %} {{ opts.class }}{% endif %}">
<img
class="image__img{% if opts.imgClass %} {{ opts.imgClass }}{% endif %}"
alt="{{ opts.alt }}"
src="{{ imgix_url(opts.url) }}"
{# only output width and height attributes for SVG if they are true, WP doesn't store this #}
{% if opts.width %}
width="{{ opts.width }}"
{% endif %}
{% if height %}
height="{{ height }}"
{% endif %}
{# if lazy, set native loading attribute #}
{% if opts.lazy %}
loading="lazy"
{% endif %}
>
</div>
{# image is not an SVG #}
{% else %}
{% set ratio = (args.ratio ?? false) %}
{% set intrinsicRatio = (height / opts.width)|round(2) %}
{% if ratio %}
{# if ratio is set, we need to ensure width and height match ratio #}
{# if desired ratio is not within 5% of intrinsic, calculate new height attribute based on provided ratio #}
{% if ((ratio - intrinsicRatio)|abs) > 0.05 %}
{% set height = args.width * ratio %}
{% endif %}
{% else %}
{# if ratio not set, use image's intrinsic ratio #}
{% set ratio = intrinsicRatio %}
{% endif %}
<picture class="image{% if opts.class %} {{ opts.class }}{% endif %}">
{% if opts.srcset %}
{# source for hi-dpi (1.5x up) displays #}
{# @see https://jakearchibald.com/2021/serving-sharp-images-to-high-density-screens/ #}
<source
media="(-webkit-min-device-pixel-ratio: 1.5)"
sizes="{{ sizes }}"
{# get and output srcset with 2x resolution but reduced quality to keep file size down #}
srcset="{{ imgix_srcset({
url: opts.url,
min: (opts.srcset.hdpiMin * 2),
max: (opts.srcset.hdpiMax * 2),
ratio: ratio,
quality: 45,
params: opts.params
}) }}"
>
{% endif %}
{# img for low-dpi (1x) displays and fallback #}
<img
class="image__img{% if opts.imgClass %} {{ opts.imgClass }}{% endif %}"
alt="{{ opts.alt }}"
{# if srcset provided #}
{% if opts.srcset %}
{# get and output srcset #}
srcset="{{ imgix_srcset({
url: opts.url,
min: opts.srcset.ldpiMin,
max: opts.srcset.ldpiMax,
ratio: ratio,
quality: 85,
params: opts.params
}) }}"
sizes="{{ sizes }}"
{% else %}
{# get URL to image on imgix with added parameters #}
src="{{ imgix_url(opts.url, opts.params) }}"
{% endif %}
{# set dimensions to prevent layout shift #}
width="{{ opts.width }}"
height="{{ height }}"
{# if lazy, set native loading attribute #}
{% if opts.lazy %}
loading="lazy"
{% endif %}
>
</picture>
{% endif %}
{% endspaceless %}
{%- endmacro %}
{##
# acf image macro that pulls from acf image array
#
# arguments:
{
// required
image,
// optional
alt,
class,
imgClass,
lazy,
width,
height,
ratio,
srcset: {
sizes,
ldpiMin,
ldpiMax,
hdpiMin,
hdpiMax,
},
params
}
#}
{% macro acfImage(args) -%}
{% if args.image %}
{% set image = args.image %}
{% set width = args.width ?? false %}
{% set height = args.height ?? false %}
{% if args.height == 'auto' and width %}
{% set height = (width / image.width) * image.height %}
{% endif %}
{% if args.width == 'auto' and height %}
{% set width = (height / image.height) * image.width %}
{% endif %}
{{ _self.image({
url: image.url,
type: image.subtype,
alt: ( args.alt ?? image.alt ?? '' ),
class: ( args.class ?? '' ),
imgClass: ( args.imgClass ?? '' ),
lazy: ( args.lazy ?? true ),
width: width|default(image.width),
height: height|default(image.height),
ratio: ( args.ratio ?? false ),
srcset: ( args.srcset ?? false ),
params: ( args.params ?? false )
}) }}
{% endif %}
{% endmacro %}
<?php
/**
* Utilities for images with imgix
*
* @package Daysmart
* @subpackage Imgix
*/
namespace Daysmart;
use Timber;
use Imgix\UrlBuilder;
class Imgix
{
/**
* __construct
*
* Add actions and filters
*
* @return void
*/
public function __construct()
{
add_filter('timber/twig', [self::class, 'add_to_twig']);
// add preconnect header
add_action('send_headers', [self::class, 'preconnect']);
}
/**
* add_to_twig
*
* Adds any functions, extensions or filters to twig.
*
* @see https://timber.github.io/docs/guides/extending-timber/
*
* @param Twig $twig Instance of Twig we add functionality to
* @return Twig Twig instance with additions
*/
public static function add_to_twig($twig)
{
$twig->addFunction(new Timber\Twig_Function('imgix_url', [self::class, 'url']));
$twig->addFunction(new Timber\Twig_Function('imgix_srcset', [self::class, 'srcset']));
return $twig;
}
/**
* url
*
* Take URL for image and add parameters
*
* @param string $url URL to image including IMGIX domain
* @param array $params Imgix parameters to add to the image
* @return string URL to image with params
*/
public static function url($url, $params = [])
{
$path = self::path($url);
if ($params) {
$params = array_merge(['auto' => 'format,compress'], $params);
}
$builder = self::builder();
$url = $builder->createURL($path, $params);
return $url;
}
/**
* srcset
*
* Generates srcset for the provided image with the provided arguments.
*
* Required args:
* - `url` — URL to image including IMGIX domain
* - `min` — Minimum size to generate
* - `max` — Maximum size to generate
* - `ratio` — Aspect ratio of image
*
* Optional args:
* - `quality` — Image compression quality from 0–100. Default 85
* - `tolerance` — Seperation between each image from 0–1. Larger values result in fewer images, lower values result in more. Default 0.08
*
* @param array $args Associative array with arguments for srcset generation
* @return string String ready to be inserted into `srcset` attribute
*/
public static function srcset($args)
{
$path = self::path($args['url']);
$min_size = $args['min'];
$max_size = $args['max'];
$ratio = '1:' . $args['ratio'];
$quality = $args['quality'] ?? 85;
$tolerance = $args['tolerance'] ?? 0.08;
$params = [
'auto' => 'format,compress',
'fit' => 'crop',
'crop' => 'center',
'q' => $quality,
'ar' => $ratio
];
if ($args['params'] ?? false) {
$params = array_merge($params, $args['params']);
}
$opts = [
'start' => $min_size,
'stop' => $max_size,
'tol' => $tolerance
];
$builder = self::builder();
$srcset = $builder->createSrcSet($path, $params, $opts);
return $srcset;
}
/**
* builder
*
* Create instance of Imgix/UrlBuilder with correct settings
*
* @return UrlBuilder
*/
private static function builder()
{
return new UrlBuilder(\IMGIX_DOMAIN, true, '', false);
}
/**
* path
*
* Convert absolute Imgix URL to relative for use with UrlBuilder
*
* @param string $url Imgix URL including domain
* @return string Relative Imgix URL without host
*/
private static function path($url)
{
$pattern = '/https?:\/\/' . \IMGIX_DOMAIN . '/';
return preg_replace($pattern, '', $url);
}
public static function preconnect()
{
header('Link: <https://' . \IMGIX_DOMAIN . '/>; rel=preconnect');
}
}
<picture class="image">
<source media="(-webkit-min-device-pixel-ratio: 1.5)"
srcset="
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=45&amp;w=600 600w,
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=45&amp;w=696 696w,
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=45&amp;w=807 807w,
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=45&amp;w=937 937w,
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=45&amp;w=1086 1086w,
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=45&amp;w=1260 1260w,
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=45&amp;w=1400 1400w"
>
<img alt="" class="image__img" height="1200" sizes="100vw"
srcset="
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=85&amp;w=700 700w,
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=85&amp;w=812 812w,
https://daysmart-s8.imgix.net/uploads/2021/06/placeholder-1.jpg?ar=1%3A0.6&amp;auto=format%2Ccompress&amp;crop=center&amp;fit=crop&amp;q=85&amp;w=837 837w"
width="2000"
>
</picture>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment