Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for supporting responsive images with srcset/<picture> element #71

Open
gingerchew opened this issue Mar 8, 2022 · 5 comments

Comments

@gingerchew
Copy link

I've been racking my brain trying to think of a way to do this other than doing n pthumb snippets in 1 chunk

The way I currently do it:

<picture>
	<source srcset="[[*img:pthumb=`w=250&q=50`]]" media="(min-width: 414px)" />
	<source srcset="[[*img:pthumb=`w=552&q=50`]]" media="(min-width: 768px)" />
	<img src="[[*img]]" alt="..." />
</picture>

But I thought what if instead each option was processed like an array similar to how CSS short hand vs expanded properties are handled.

#app {
	transition: all 0.25s ease;
	transition-property: opacity, transform;
	transition-duration: 0.25s, 0.5s;	
}

Meaning that when passed the options, like this:

[[pthumb? &options=`w=250,552&q=50` &input=`[[*img]]` &toPlaceholder=`pt-img`]]

Then two images would be generated, but only calling the snippet once. This might mean that it only works with something like toPlaceholder, but maybe thats a fair trade off?

Then to differentiate the images, I see two paths. One is my personal dream scenario, the other is what i imagine is the easier solution.

The first would be being able to also add a comma separated list to the &toPlaceholder option:

[[pthumb? &options=`w=250,552&q=50` &input=`[[*img]]` &toPlaceholder=`pt-img-sm,pt-img-md`]]
[[+pt-img-sm]]
[[+pt-img-sm.width]]
[[+pt-img-md]]

The second one would be to suffix it with a number [[+pt-img-1]] or to put a number as the placeholder [[+pt-img.1]] :

// option one
[[pthumb? &options=`w=250,552&q=50` &input=`[[*img]]` &toPlaceholder=`pt-img`]]
[[+pt-img-1]]
[[+pt-img-1.width]]
[[+pt-img-2]]
// option 2
[[pthumb? &options=`w=250,552&q=50` &input=`[[*img]]` &toPlaceholder=`pt-img`]]
[[+pt-img.1]]
[[+pt-img.1-width]] /* or */ [[+pt-img.1_width]]
[[+pt-img.2]]

This hit me on the drive home and so I wanted to float it to see if it was interesting to anyone else, in scope, or even possible for that matter.

@chrisdempsey
Copy link

@gingerchew - did you ever develop a solution for this?

I took an alternative approach - a separate snippet to calculate the responsive image requirements to generate a <picture> element, calling phpThumbOf to create images for each size.

It requires the breakpoints of the css grid system you are using (Bootstrap, Tailwind etc.) are predefined and for the column width your image will be at each breakpoint to be specified. It then creates jpg and webp versions of the image for each breakpoint and accepts a number of arguments including aspectRatio, pixelDensity (for x2 images), imgQuality, cssClass.

Example usage is below.

My implementation has several todo's outstanding and the code could be tidier.

I'm happy to share the Snippet if you were interested in working on it if you think it would be useful for the Community?

[[generate-grid-picture?
    [[- responsiveConfig: breakpoint: cols (at that breakpoint), aspectratio ]]
    &src=`[[*img-super-hero]]`
    &responsiveConfig=`
    {
    	"xs": ["12", "2x1.5"],
    	"sm": ["12", "2x1"],
    	"md": ["12", "2x1"],
    	"lg": ["12", "3x1"],
    	"xl": ["12", "3x1"]
    }
    `
    &alt=`[[*img-super-hero-alt]]`
    &width=`` [[- if set is the maximum width the <picture> will display, even with .img-fluid set ]]
    &class=`[[+class-img]]` [[- passed in chunk call ]]
    &loading=`lazy` [[- loading attribute value, options: lazy|eager ]]
    
    [[- phpthumbof attributes ]]
    &zc=`1`  [[- zc = zoom-crop. auto-crop the larger dimension (requires both "w" and "h", overrides  "far"). NOTE: only 1 or C works (need ImageMagik for direction) - Set to "1" or "C" to zoom-crop towards the center, or set to "T", "B", "L", "R", "TL", "TR", "BL", "BR" to gravitate towards top/left/bottom/right directions ]]
    &aoe=`1` [[- aoe = Output Allow Enlarging - ("far" overrides this) ]]
    &far=`1` [[- Force Aspect Ratio - image will be created at size specified by "w" and "h" (which must both be set)]]
    
    &type=`jpg` [[- jpg or svg, svg is used to skip <source> element outputs ]]
    &webp=`true` [[- if &webp=true snippet outputs webp <source> elements as well as &type ]]
    &quality=`80`
    &pixeldensity=`2` [[- set to 2 or higher to output high dpi images for @2x etc. ]]
    
    [[- grid attributes ]]
    &gridcols=`12` [[- used to set $grid_columns in snippet, defaults to 12 ]]
    &gridgutter=`30` [[- used to remove gutter from container width so $resized_img_width is available size, not full grid width ]]
    
    &debug=``
]]

@gingerchew
Copy link
Author

That does a bit more than I need, but if you wanna share the pthumb parts of it I'd love to see

@chrisdempsey
Copy link

chrisdempsey commented Mar 31, 2023

That does a bit more than I need, but if you wanna share the pthumb parts of it I'd love to see

Leave this with me for a bit, I'll dig out the code and post it here.

@gingerchew
Copy link
Author

Finally got fed up enough to give it a shot myself, this is what I came up with.

<?php
/**
 * Generate the sizes and srcset attributes automatically
 * based on passed options similar to the pthumb &options field
 */
if (empty($input)) {
    return;
}

$pt_settings = [];

if (empty($pt_settings)) {
    if (!$modx->loadClass('phpthumbof', MODX_CORE_PATH . 'components/phpthumbof/model/', true, true)) {
        $modx->log(modX::LOG_LEVEL_ERROR, '[imageSizes] Could not load phpThumbOf class.');
        return 'Could not load phpthumbof for '.$input;
    }
}

$pThumb = new phpThumbOf($modx, $pt_settings, $scriptProperties);

$bp_options = $modx->getOption('bp', $scriptProperties);
$img_options = $modx->getOption('options', $scriptProperties);

if (!empty($img_options)) {
    $img_options = explode(',', $img_options);
}

$sizesAttr = 'sizes="';
$srcsetAttr = 'srcset="';
$srcAttr = 'src="';

if (!empty($bp_options)) {
    
    $i = 0;
    foreach(explode(',', $bp_options) as $breakpoint_option) {
        
        $src = $pThumb->createThumbnail($input, $img_options[$i].'&dims=1');
        
        $dims = getimagesize($src['file']);
        
        $src['width'] = $dims[0];
        $src['height'] = $dims[1];
        
        $sizesAttr .= '(min-width: '.$breakpoint_option.'px) '.$src['width'].'px';
        $srcsetAttr .= $src['src'].' '.$src['width'].'w';
        $i += 1;
    
        if ($i != sizeof($img_options)) {
            $sizesAttr .= ', ';
            $srcsetAttr .= ', ';
        } else {
            $srcAttr .= $src['src'].'"';
        }
    }
}

$sizesAttr .= '" ';
$srcsetAttr .= '" ';
$output = $sizesAttr . $srcsetAttr . $srcAttr;

return $output;

Then you would use it like this

[[imageSizes? &input=`path/to/image/file.jpeg` &bp=`1200,576` &options=`w=500,q=60&w=400`]]

I'm not really a modx/php dev, so if there is any input/improvements, I'm open to them

@chrisdempsey
Copy link

My apologies, I clean forgot about this. I intended to clean up the snippet before posting it publicly but have been flat out on other tasks.

Here's my version.

Much of it needs tidied, the debugging effort is far too repetitive (it can add text to the image output to help check what the responsive images are doing) and the output should really be built from a template chunk with placeholders.

Snippet [[generate-grid-picture]]

<?php
/**
     * &figure flag to set whether to wrap output <picture> with <figure> element: int, default 0
     * &figcaption the text description to add to the <figcaption> element: string, not required
     * 
     * &width, required - if set as blank ie. &width=`` in snippet call this script holds that and does not catch the default value for $img_width at line #53 (currently 640).
     *                    this means the <img src="" fallback at line #165 gets no value and phpthumbof returns the full size source image
     * 
     * TO DO
     * - allow full width fluid dimensions for images that are not in central grid container eg. superheroes
     * - hd images on fallback <img> tag
     * - validate html
     * - validation/sanity checks - make sure all variables passed in snippet are valid format, otherwise log error (or write to screen if &debug=`true`)
     * - prevent images being resized larger than source?  no, aoe is optional so that @2x can be generated from smaller source, article said output is still better on hd screen than usine a @1x image
     * - tpl/placeholder for <figure> and <figcaption>
     * - check and document how to adjust config for fluid/p-0 container cols so gutter/margin is correct
     * - test with png image as src
     * 
     * 
     * ISSUES
     * - desktop screen shows 2x image but should be 1x
     * - can be slow on first run as up to 15 images are created by pThumb
     * - should return output using chunk and placeholders as template ie. no html in snippet code
     * 
     * 
     * phpThumbOf readme: http://phpthumb.sourceforge.net/demo/docs/phpthumb.readme.txt
     * 
     * inspired by a gist by Nathanael McMillan: https://gist.github.com/NatemcM/92d31822274b0074912383aac1b32269
     * 
     */
     
     /**
      * example use
      * 
      * [[generate-grid-picture? &src=`[[*mytv]]` &alt=`my alt tag` &width=`400` &lazy=`true` &class=`myclass` &responsiveConfig=`` &type=`jpg` ]]
      * 
      */
    

    /**
     * 
     * functions
     * 
     */
    
    /* calcHeight
        - calculates height from supplied column width and specified aspect ratio
    */
    if(!function_exists('calcHeight')) {
        function calcHeight($width, $ratio) {
            $arr_ratio = explode('x', $ratio);
            $height = ($width / $arr_ratio[0]) * $arr_ratio[1];
            $height = ceil($height);
            
            // echo 'w=' . $width . ', h=' . $height . PHP_EOL;
            
            return $height;
        }
    }
    

    /**
     * 
     * snippet
     * 
     */

    // get the image src path
    
    /* - getOption returns xs_img-placeholder with a leading forward slash
         - Client Config isused to store file path for xs_img-placeholder
         - database shows the expected value: skin/placeholder-awaiting-image-800x600.png
         - as expected, calling $modx->getOption prefixes the value returned with the media source basePath: assets/media/
         - BUT the value returned has an extra forward slash: /assets/media/skin/placeholder-awaiting-image-800x600.png
         - the slash is the base URL ([[++base_url]]) that gets added to the path
         - fix by trimming the slash
         
         ref. https://community.modx.com/t/forward-slash-added-to-value-returned-by-modx-getoption-in-snippet/6806/2?u=chris.dempsey78
         
         - same applies for any other call to [[generate-grid-picture]] eg. from [[picture-postcard] which calls Chunk: picture-postcard (115)
           which in turn calls generate-grid-picture?, need to strip leading slash in all cases
    */

    // image src path, default to xs_img-placeholder if src not sepcified
    $img_src = $modx->getOption('src', $scriptProperties, $modx->getOption('xs_img-placeholder'));
    // trim leading slash if it exists
    $img_src = ltrim($img_src, "/");

    // image alt tag, no default
    $img_alt = $modx->getOption('alt', $scriptProperties);
    
    // image width, no default. used for width attribute on <img> tag. if specified this sets max width of image (.img-fluid won't go past this even if space allows)
    $img_width = $modx->getOption('width', $scriptProperties);

    // $img_fluid - switch to indicate if image is full width of browser or within grid container
    // do not confuse with bootstrap class .img-fluid
    $img_fluid = $modx->getOption('fluid', $scriptProperties, '0');

    // loading attribute value for <img> tag, options: lazy|eager, default lazy - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img
    $img_loading = $modx->getOption('loading', $scriptProperties, 'lazy');

    // class to add to <img>, default is .img-fluid for bootstrap
    $img_class = $modx->getOption('class', $scriptProperties, 'img-fluid');
    
    // attributes to add to <img> eg. &attributes=`data-aos="flip-left" data-aos-duration="1000"`
    $img_attributes = $modx->getOption('attributes', $scriptProperties, '');
    
    // sets the number of columns assigned to the image at each breakpoint.
    // default to 12 cols at 1x1 ratio as can't predict the config required for a particular situation but setting something allows the snippet to work if no value set in snippet call
    $img_responsiveConfig = $modx->getOption('responsiveConfig', $scriptProperties, '{ "xs": ["12", "1x1"], "sm": ["12", "1x1"], "md": ["12", "1x1"], "lg": ["12", "1x1"], "xl": ["12", "1x1"] }');
    
    // fixes the output image type ie. if the src image is a png file and $img_type is `jpg` the output will be .jpg, default is jpg - note, webp type is hard coded below, currently it's always included in the output
    // TODO - see note above to add switch for webp outpuut
    $img_type = $modx->getOption('type', $scriptProperties, 'jpg');
    
    // image quality setting
    $img_quality = $modx->getOption('quality', $scriptProperties, '80');
    
    // include webp set in output, default is false
    $img_webp = $modx->getOption('webp', $scriptProperties, 'false');
    
    // pixel density, int, eg. 1, 2 or 3 used to generate hd images, default: 1
    $img_density = $modx->getOption('pixeldensity', $scriptProperties, '1');
    
    // zoom crop, default: 0
    $img_zc = $modx->getOption('zc', $scriptProperties, '0');

    // allow output enlarging, default: 0
    $img_aoe = $modx->getOption('aoe', $scriptProperties, '0');

    // force aspect ratio, default: 0
    $img_far = $modx->getOption('far', $scriptProperties, '0');
    
    // grid columns (number of columns available in the css grid), default to 12 for bootstrap
    $grid_columns = $modx->getOption('gridcols', $scriptProperties, '12');
    
    // grid gutter (gutter of the css grid), default to 30 for bootstrap
    $grid_gutter = $modx->getOption('gridgutter', $scriptProperties, '30');
    
    // check if we will wrap with <figure> element
    $img_figure = isset($figure) && intval($figure) ? $figure : 0;
    
    // bol to set debug flag - defaults to false. debug writes the file WIDTH on resized images
    $is_debug = $modx->getOption('debug', $scriptProperties, false);

// *** temporarily set debug=true
// $is_debug = 'true';
    
    
    // if &responsiveConfig=`` if blank in snippet call this doesn't set $img_responsiveConfig value to default base set, $img_responsiveConfig becomes '' so no source elements are added in loop
    /* - not required to set this back to base, default behaviour is required to output a single image which is useful for small images eg. in col-* sizes of 1-3
    if(empty($img_responsiveConfig)) {
        // if $img_responsiveConfig is empty set it to base json from initial $img_responsiveConfig above
        $img_responsiveConfig = '{"xs":12,"sm":12,"md":8,"lg":6,"xl":6}';
    }
    */
    
    // if debug flag set, dump scriptProperties to screen
    if($is_debug) {
        echo '<code>';
        echo '  <h4>$scriptProperties</h4>';
        print_r ($scriptProperties);
        echo '</code>';
    }
    
    // if $img_type is svg just return the vector image, it can be scaled without loss so no need to provide multiple resized versions
    if ($img_type == 'svg') {
        $output_img = '<img src="' . $img_src . '" alt="' . $img_alt . '" class="' . $img_class . '" width="' . $img_width . '" loading="' . $img_loading . '" ' . $img_attributes . ' />';
        return $output_img;
    }
    

  /**
    * map breakpoint info to array by name (ie. xs,sm,md...)
    *
    * - width_fluid: (max screen width achievable for current browser size)
    * - width_grid: (outer grid width)
    * - width_container (container width after gutter/padding)
    * - media: 
    * 
    * note: order elements are defined in the array does not matter, when required they are called by key eg. xs,sm,md...
    *
    */

    $breakpoint_info = array(
        "xs" => array(
            "width_fluid" => 576,
            "width_grid" => 575,
            "width_container" => 575,
            "media" => '(max-width: 575.98px)'
        ),
        "sm" => array(
            "width_fluid" => 768,
            "width_grid" => 576,
            "width_container" => 540,
            "media" => '(min-width: 576px) and (max-width: 767.98px)'
        ),
        "md" => array(
            "width_fluid" => 992,
            "width_grid" => 768,
            "width_container" => 720,
            "media" => '(min-width: 768px) and (max-width: 991.98px)'
        ),
        "lg" => array(
            "width_fluid" => 1200,
            "width_grid" => 992,
            "width_container" => 960,
            "media" => '(min-width: 992px) and (max-width: 1199.98px)'
        ),
        "xl" => array(
            "width_fluid" => 1920,
            "width_grid" => 1200,
            "width_container" => 1140,
            "media" => '(min-width: 1200px)'
        )
    );
    

    // if debug flag set, dump breakpoint_info to screen
    if($is_debug) {
        echo '<code>';
        echo '  <h4>$breakpoint_info</h4>';
        print_r ($breakpoint_info);
        echo '</code>';
    }


    // create array $arr_responsiveConfig - but only if $img_responsiveConfig is not empty
    if(!empty($img_responsiveConfig)) {
        $arr_responsiveConfig = json_decode($img_responsiveConfig);
        
        // if debug flag set, dump $arr_responsiveConfig to screen
        if($is_debug) {
            echo '<code>';
            echo '<h4>$arr_responsiveConfig</h4>';
            print_r ($arr_responsiveConfig);
            echo '</code>';
        }
    }

    // define array to hold <source> elements
    $arr_source_elements = [];

    // loop only runs if $arr_responsiveConfig exists, otherwise $output is just the fallback <img> added at the end - *** check this, should probably be if $arr_responsiveConfig exists then run foreach ***
    foreach ($arr_responsiveConfig as $breakpoint => $attributes) {
        // $breakpoint = bootstrap breakpoint name eg. xs,sm,md...
      
        // set $grid_column_factor - used in if statement to calculate the width to resize the image from the total width available at each breakpoint
        // a value of 1 would be full width, higher than 1 means smaller than full width eg. 1200 / 2 (col-6) = 600px
        $grid_column_factor = $grid_columns / $attributes[0];
            // $grid_columns is the total columns available to the grid ie. 12 for bootstrap
            // $attributes[0] is the number of columns available to the current size as specified in &responsiveConfig json eg. xl is 12 - "xl": ["12", "1.6x1"]
      
        // establish $col_width depending if &fluid=`1`
        // if fluid=1 image is full width of browser
        // if fluid=0 we're in the grid container so calculate width from grid size
        // but...
        // generate-grid-picture can be called either inside or outside grid container eg. full browser width super-hero or image in grid column
        // therefore need if/else to figure the current $col_width
        if ($img_fluid == '1' ) {
          // full browser width
          $col_width = floor($breakpoint_info[$breakpoint]['width_fluid'] / $grid_column_factor);
          // echo '<br /> ct_fluid.col_width = ' . $col_width;
          // $col_width = $breakpoint_info[$breakpoint]['width_fluid']; echo ' || 1.col_width = ' . $col_width;
        } else {
          // calculate width from grid parameters, includes allowance for $grid_gutter which reduces the width available width inside the column
          $col_width = floor($breakpoint_info[$breakpoint]['width_container'] / $grid_column_factor); // floor to round down because some widths come back at eg. 548.6666666666
          $col_width = $col_width - $grid_gutter;
          // echo '<br /> ct_grid.col_width = ' . $col_width;
        }


        
        // if debug flag set, write calculations to screen
        if($is_debug) {
            
            echo '<code>';
            echo '<h4>' . $breakpoint . ' calculations</h4>';
                echo PHP_EOL;
                echo 'breakpoint = ' . $breakpoint  . '<br />' . PHP_EOL;
                echo 'breakpoint_info[width_grid] = ' . $breakpoint_info[$breakpoint]['width_grid'] . '<br />' . PHP_EOL;
                echo 'breakpoint_info[width_container] = ' . $breakpoint_info[$breakpoint]['width_container'] . '<br />' . PHP_EOL;
                echo 'cols = ' . $attributes[0] . '<br />' . PHP_EOL;
                echo 'ratio = ' . $attributes[1] . '<br />' . PHP_EOL;
                echo 'resized_img_width (col_width) =  ' . $col_width . '<br />' . PHP_EOL;
            echo '</code>';            
        }
        
        /**
         * 
         * generate <source> elements and add to $arr_source_elements
         * 
         */

            // define array to hold image formats we want to output, will be $img_type specified and may additionally include webp if that &webp=`true` in snippet call
            $arr_formats = [];
            
            // add type specified in snippet call eg. &type=`jpg`
            $arr_formats[] = $img_type;
            
            // add webp if required (jpg/png first because $arr_source_elements is reversed later which puts outputs webp first, this is required so webp capable devices select the webp image over jpg/png)
            if($img_webp == 'true') {
                $arr_formats[] = 'webp';
            }
            
            // loop through each image format and create <source> elements
            foreach ($arr_formats as $format) {
                        
                // define $options for phpthumbof call, start without .= concatenation to ensure $options is reset
                $options  = '&zc='  . $img_zc; 
                $options .= '&aoe=' . $img_aoe;
                $options .= '&far=' . $img_far;
                $options .= '&q='   . $img_quality;
                $options .= '&f='   . $format;
                
                // create image files for each density with phpthumbof and store src in dynamic variables
                for ( $i = 1; $i <= $img_density; $i++) {
    
                    // calculate height based on ratio supplied in $attributes[1]
                    $height = calcHeight($col_width, $attributes[1]);
    
                    // if debug flag set, dump $arr_responsiveConfig to screen
                    if($is_debug && $i == 1) {
                        echo '<code>';
                        echo '  <strong>Calculated image dimensions </strong>';
                        echo    $col_width . 'x' . $height . ' @' . $attributes[1] . '<br>' . PHP_EOL;
                        echo '</code>';
                    }                
    
                    // set width and height, multiply each by loop counter to increase density
                    $options .=  '&w=' . $col_width * $i;
                    $options .=  '&h=' . $height * $i;
                    
                    // watermark image if debugging
                    if($is_debug) {
                        $options .= "&fltr[]=wmt|BREAK $breakpoint DIMENSIONS $col_width x $height, @$i $format|5|T|FFFFFF||100|20|0||0|"; // use double quotes to catch variable $col_width
                    } 
                    
                    // dump options to screen if debugging
                    if($is_debug && $i == 1) {
                        echo '<code>';
                        echo '  <strong>Options</strong> ' . $options;
                        echo '</code>';
                    }  
    
                    // create dynamic variables to hold hd image sources eg. $hd1, $hd2, $hd3
                    // dynamic variable explained at https://stackoverflow.com/a/17135290
                    ${"$format$i"} = $modx->runSnippet("phpthumbof", array('input' => $img_src, 'options' => $options));
                    
                    // write info to log
                    // $modx->log(modX::LOG_LEVEL_ERROR, 'myerrormessage: $img_src: '.$img_src, '', 'generate-grid-picture', __FILE__, __LINE__);
                }
                
                // build the $source element
                $source = '<source media="' . $breakpoint_info[$breakpoint]['media'] . '"';
                
                // reset $srcset before loop
                $srcset = '';
                
                // build value for srcset, may have multiple images depending on pixeldensity set
                for ($i = 1; $i <= $img_density; $i++) {
                    $srcset .= ${"$format$i"} . ' ' . $i . 'x';
                    // add comma if this is not the final loop
                    if($i < $img_density) $srcset .= ', ';
                }
    
                // add srcset and type attributes then close the source element
                $source .= ' srcset="' . $srcset . '"' . ' type="image/' . $format .'" >';
    
                // add source element to array
                $arr_source_elements[] =  $source;
            
            } // end foreach ($arr_formats as $format)


    } // end foreach $arr_responsiveConfig
    

    /* set options for the fallback <img> */
    $options  = '&zc='  . $img_zc; 
    $options .= '&aoe=' . $img_aoe;
    $options .= '&far=' . $img_far;
    $options .= '&q='   . $img_quality;
    $options .= '&w='   . $img_width; // no value for &h, phpthumbof will calculate this based on other parameters
    
    // set fallback format with preference for webp
    if($img_webp == 'true') {
        $options .= '&f=webp';
    } else {
        $options .= '&f=' . $img_type;
    }
    
    
    // add watermark if debugging
    if($is_debug) {
        $options .= "&fltr[]=wmt|FALLBACK $breakpoint WIDTH $img_width, @1x|5|T|FFFFFF||100|20|0||0|"; // use double quotes to catch variable $img_width
    }
    

    // reverse the order of $arr_source_elements
    // current order is set by &responsiveConfig=`{"xs":12,"sm":12,"md":10,"lg":6,"xl":4}` which is ordered by ascending size. browser uses the first <source> it is capable of meaning it shows the smallest image.
    // Need to reverse array to get largest first
    $arr_source_elements = array_reverse($arr_source_elements);
    
    // now add fallback <img> for browsers that don't support <picture>
    $arr_source_elements[] =  '<img src="' . $modx->runSnippet("phpthumbof", array('input' => $img_src, 'options' => $options)) . '" alt="' . $img_alt . '" class="' . $img_class . '" width="' . $img_width . '" loading="' . $img_loading . '" ' . $img_attributes . ' />';


    /**
     * start buiding output
     */
     
    $output = '';
    
    // open <figure> element if required
    if($figure ==1) { $output = '<figure>' . PHP_EOL; }
    
    // open <picture> element if &responsiveConfig was set in snippet call, otherwise we just output a single <img> element
    if(!empty($img_responsiveConfig)) {
        $output .= PHP_EOL . '<picture>' . PHP_EOL;
    }

    // output source elements
    foreach ($arr_source_elements as $source) {
        $output .= $source . PHP_EOL;
    }
                    

    // close <picture> element if necessary
    if(!empty($img_responsiveConfig)) {
        $output .= '</picture>' . PHP_EOL;
    }
    
    // add figcaption if supplied
    if(!empty($figcaption)) {
        $output .= PHP_EOL . '<figcaption class="font-italic text-secondary my-3">' . nl2br($figcaption) . '</figcaption>' . PHP_EOL;
    }
    
    // close <figure> element if required
    if($figure ==1) { $output .= '</figure>' . PHP_EOL; }
    
    return $output;

Example use

This is part of a template chunk so includes placeholders.

<div class="col-12 my-5">
    [[generate-grid-picture?
        [[- responsiveConfig: breakpoint: cols (at that breakpoint), aspectratio ]]
        &src=`[[+image]]`
        &responsiveConfig=`
        {
         "xs": ["12", "16x9"],
         "sm": ["12", "16x9"],
         "md": ["12", "16x9"],
         "lg": ["12", "16x9"],
         "xl": ["12", "16x9"]
        }
        `
        &alt=`[[+alt]]`
        &width=`` [[- if set is the maximum width the <picture> will display, even with .img-fluid set ]]
        &class=`img-fluid`
        &loading=`lazy` [[- loading attribute value, options: lazy|eager ]]
        
        [[- phpthumbof attributes ]]
        &zc=`1` [[- zc = zoom-crop. auto-crop the larger dimension (requires both "w" and "h", overrides "far"). NOTE: only 1 or C works (need ImageMagik for direction) - Set to "1" or "C" to zoom-crop towards the center, or set to "T", "B", "L", "R", "TL", "TR", "BL", "BR" to gravitate towards top/left/bottom/right directions ]]
        &aoe=`1` [[- aoe = Output Allow Enlarging - ("far" overrides this) ]]
        &far=`1` [[- Force Aspect Ratio - image will be created at size specified by "w" and "h" (which must both be set)]]
        
        &type=`jpg` [[- jpg or svg, svg is used to skip <source> element outputs ]]
        &webp=`true` [[- if &webp=true snippet outputs webp <source> elements as well as &type ]]
        &quality=`80`
        &pixeldensity=`2` [[- set to 2 or higher to output high dpi images for @2x etc. ]]
        
        [[- grid attributes ]]
        &gridcols=`12` [[- used to set $grid_columns in snippet, defaults to 12 ]]
        &gridgutter=`30` [[- used to remove gutter from container width so $resized_img_width is available size, not full grid width ]]
        
        &debug=``
    ]]
</div>

Depending how you use it you would need to replace the [[+placeholder]]'s with actual values eg.

&alt=`[[*img-alt]]`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants