Category: Web Development

  • Responsive Background Images Part 2: Harnessing element() For The Future

    In the first part, Responsive Background Images With or Without image-set(): The Proven Way looked at the current way to make background images responsive. It is not without it’s down sides. I will discuss these and present a new way to use an img tag, or any tag, as a responsive – and even dynamic and animated – background for any element.

    image-set() Con: Existing Content Management System Blocks/Extensions

    Block elements and extensions as well as built-in features within existing Content Management Systems (CMS) are all designed to use previous and current generation CSS. Image-set() is not meant as a translation of the img tag’s srcset and sizes attributes. Media queries balloon out the size of CSS and are difficult to update and control. Extending existing blocks and extensions don’t always catch every case and many third-party-developed extensions may not even be modifiable.

    Many people will continue to use the existing elements and blocks they are familiar with. This means many existing sections won’t be able to make use of new ways to make backgrounds responsive.

    Responsive Background Images In The Future: The element() Function

    A new CSS function will allow developers to use an old and familiar pattern to easily provide background images that respond and update to size of the viewport of container. Additionally, this function allows the use of more than just images. Animations, dynamically updating content and videos can also be rendered as the background of an element.

    The element() CSS function allows you to use any element on the page as the background of any other element. That means images that response responsively to the viewport or container queries. That also means any element with any content – text, images/canvas, video, or dynamically updated and animated content.

    This function also allows developers to bypass existing CSS generations and implement an entirely new background image using an img tag, even a tag that already exists within the site – such as a featured or headline image.

    element() Browser Support: Just Beginning

    As of August 2024, the only browser that supports the element() function is Firefox. Why so little support? This is a new function, part of the CSS Images Modules Level 4, and there are still some issues to be addressed. Other browsers will start supporting it as the issues are ironed out.

    Here is an example (as of August 2024, this will only work in Firefox):

    Close-up photo of various sticky notes showing various web layout designs next to some pens with a keyboard to the right, above them. They are on a dark brown wooden desk.
    HTML
    <div id="element-background-test"></div>
    <figure id="element-background-test-image-wrapper">
      <img id="element-background-test-image" ... />
    </figure>
    <style>
    #element-background-test {
        background: -moz-element(#element-background-test-image) no-repeat;
        background: element(#element-background-test-image) no-repeat;
        background-position: center center;
        background-size: cover;
        min-height: 80vh;
        border: 4px solid black;
    }
    #element-background-test-image-wrapper {
        height: 0;
        overflow: hidden;
    }
    </style>

    Granted, this is rather winded, but not as much as when using CSS media queries. The img tag can more easily be generated by most CMSes while I have not seen any that generate media queries.

    You can mimic this today, in all browsers, using a stack.

    Check back here for a link to part 3.

  • Responsive Background Images With or Without image-set(): The Proven Way

    Responsive Background Images With or Without image-set(): The Proven Way

    Do a search for “responsive background images” on the web. Go on and do the search, I’ll wait.

    Oh, you’re back. Notice anything strange? For example, nobody can decide on what this means, how to do it, or even how to do it easily? You’re not alone. Fact is, the vast majority of sites have incorrect answers for generating responsive background images the way the img tag with srcset and sizes attributes work. This post puts it all to rest with the definitive answer a few mention as an “option” rather than the answer. Let’s start with a definition.

    What are responsive background images?

    There is some clear confusion over what exactly constitutes a responsive background image. Many declare them, simply, as the act of using a background image and that they are inherently responsive. Others define them as the filling of a given space by setting the background-size property to cover or contain. This inconsistency makes the entire idea confusing; They define the background styling an HTML element but not making the background responsive.

    Responsive background images are to background images what the srcset and sizes attributes are to the img tag. Mozilla’s web development docs define that as

    images that work well on devices with widely differing screen sizes, resolutions, and other such features

    MDN

    In other words – just adding a background image to an element or even having it fill a given space does not constitute a responsive background image… but it does contribute to it.

    Image-Set() Is For Pixel Density & Type Support, Not Responsiveness

    Many posts will erroneously tell you to use image-set() to make background images responsive. This is misleading at best. image-set() was not meant to be used to provide different images at differing device screen widths. It is meant to provide different images based on the pixel density of the device. I already know what some are going to say…

    But pixel density does fit with device size!

    Unfortunately, you would be wrong. Using pixel density (such as DPI or 1x, 2x, 3x, etc) as a way to target a mobile device vs a tablet vs a desktop won’t work. While most mobile phones do use higher pixel density than most desktop monitors, others do as well. A number of tablets also have higher pixel density and Apple’s high DPI Retina display is (or was) available on their desktop machines as well. Just because the vast majority of the rest of the industry did not go in that direction does not mean you should simply assume only certain devices make use.

    What does this all mean? Using image-set() by itself as a way to define device-specific images is not likely to get the results you expect and does not mean your background images are responsive. The following code will not hit the majority of cases you would expect to hit when setting different sized images for mobile devices, tablets, and desktops:

    CSS
    // Don't use this to target mobile, tablet, or desktop devices
    .some-element {
      background-image: image-set(
        url("/path/to/regular-image.webp") 1x,
        url("/path/to/regular-image.2x.webp") 2x
      );
    }

    Responsive Background Images Revealed: The Proven & Tested Solution

    While various articles and StackOverflow (SO) question answers may tell you to multiple background-image properties (possibly using some JavaScript-set class), use image-set() by itself, or some other JavaScript (JS) to swap-out images, DON’T. They all have huge downsides. Everything from loading multiple versions of image, which uses even more bandwidth and loading a single image, to delaying the loading of images to increasing the amount of blocking scripts unnecessarily. There is a much better, pure CSS way to handle responsive background images.

    The tested and proven way is to use media queries. Yep, I said it. You may not like it, but as of 2024, it’s the primary way to generate responsive background images that target different device widths the your breakpoints and img tags do. Here is the tested and proven solution, JS involved:

    CSS
    /* Use your own breakpoints or, better yet, generate
       this dynamically within your CMS (such as through
       an extension/plugin or theme):
     */
    
    // Mobile first! Up to 767px:
    .some-element {
      background-image: url("/path/to/image-767.webp");
    }
    
    // Tablet (768px to 1023px)
    @media (min-width: 768px) {
      .some-element {
        background-image: url("/path/to/image-1023.webp");
      }
    }
    
    // Smaller laptops and tablet in landscape (1024px to 1279px)
    @media (min-width: 1024px) {
      .some-element {
        background-image: url("/path/to/image-1279.webp");
      }
    }
    
    // Standard laptops and desktops FULL HD (1280px and up)
    @media (min-width: 1280px) {
      .some-element {
        background-image: url("/path/to/image-2048.webp");
      }
    }
    
    // You could include more queries if you are targeting
    // 2K, 4K, 8K screens, etc.

    The code above will allow your site to display responsive background images to varying device sizes while reducing bandwidth for smaller devices. You can even use art direction. For instance, if your desktop layout has the element only needed in 500px wide background, you can set your larger display to only download the smaller size image. It is the img tag equivalent of:

    HTML
    <img
        src="/path/to/image-767.webp"
        srcset="/path/to/image-768.webp 767w, /path/to/image-1024.webp 1023w, /path/to/image-1280.webp 1279w, /path/to/image-2048.webp 1280w"
        sizes="100vw">

    You can even use image-set() in addition to the media queries to target both device sizes as well as devices with higher pixel density/DPI/PPI. Performance analyzers like Lighthouse’s Pagespeed Insights will also thank you for taking the responsive route for background images.

    Below is an example:

    Responsive Background Images: Alternative Ways

    In part 2 you learn a new way to achieve responsive background images without using the potentially three, four, five or more media queries.

  • IMG Sizes Attribute Explained: Boosting Image Optimization

    IMG Sizes Attribute Explained: Boosting Image Optimization

    Your template’s design layout often has images that do not fill the entire width of all devices your visitors may be using. CSS’s grid layouts and flexbox layouts allow even greater control over the design of websites. Using img sizes attribute to hint to the browser the maximum size the image will render at allows developers to reduce bandwidth and fit images to their design rather than the device’s width.

    A number of articles discussing this idea, or at least something similar to it, talk about art direction but this idea is different. Most suggest wrapping the img element with a picture element with customized source elements. This allows you do do things like replace the image based on the device size, but requires duplicating the image URL in order to use it to simply define a maximum image size for the design based on device width. This is overkill for simple control of the image resolution.

    Content Management Systems (CMS) and blog software including Drupal modules, WordPress and it’s popular plugins have a default of using the device width. They have no understanding of the design’s layout. This is problematic as not all images would benefit from it and some, like icons and logos, may end up causing browsers to download the wrong resolution and leads to images that are either too large or too small for the space the image fills. For most CMSes, the best solution is to simply define them all to be based on the width of the device since it’s extremely rare that images placed on the site would exceed that resolution.

    For example, here is an image grid where the img sizes attribute is set to 100px:

    You can see the images are all low resolution, even on a desktop display. Now let’s set the img sizes attribute to 100vw (100% the viewport width):

    You can see that the images are now crisp and easy to view… but there’s still a problem. They are downloading very large images even though their width is 33% of the screen… minus the padding as well. The browser is downloading images far larger than it needs to. This uses more bandwidth than needed and is slow on mobile devices with slower wireless internet connections.

    Customize The IMG sizes Attribute For Each Image Element

    Each image element tag needs the sizes attribute added (or replaced) with the customized version. In some cases this can be tedious, but there are things you can do to make the process a little easier on yourself.

    Preset, Named img sizes Attribute Values

    One way to achieve this is to create a way to store predefined sizes attributes. Make it easier by giving them unique, simple names. For instance, consider a design that has a section that will show up to 5 columns on a 2K or greater screen, 4 columns on a 1920px desktop screen, 2 columns on a tablet, and a single column mobile phones. Here is an example: (max-width: 640px) 100vw, (max-width: 1024px) 50vw, (max-width: 1920px) 25vw, 20vw.

    You could call this value “5-column” with the value set to what you would like the img sizes attribute to be, and embed that into all of the img sizes attribute. Instead of trying to remember the value of sizes or copying and pasting it over and over again, you simply tell it to include the 5-column named value. This also has the additional benefit of being able to change many images’ sizes attribute at the same time if that section of the design were to change in the future.

    Additional img sizes Considerations

    Lightbox & Other Full-screen Modal Image/Gallery Scripts

    These scripts take an existing image and “blow it up” to display it full-screen. This is to make images that may be hard to see large enough that the viewer can recognize subjects and possibly text within the image. However, altering the img sizes attribute can potentially be problematic. For instance, your desktop design calls for a 300 pixel (px) wide display and the lightbox script full-screens that image. They appear horribly artifacted/pixelated when displayed at 300px wide on a 1920px wide screen. It would appear even worse on a 2K or 4K monitor. You want to make sure the img tag used by the enlargement script does not simply copy the sizes attribute you have assigned on the site.

    How to ensure browsers will download the full-screen version of images when they are enlarged? This is a tricky answer. There are a number of different lightbox and modal image enlargement scripts. Some are forks of existing scripts but and many are independently developed and lack the same features. In general, you want to make sure to have a custom sizes attribute available in this case. I don’t see any of the major scripts’ documentation showing any data-sizes attribute or similar, so you will likely need to hook the modal’s open event and swap out the sizes attribute yourself.

  • PHP INTL: Fix Missing Or Incorrect Time-zone Offset

    PHP INTL: Fix Missing Or Incorrect Time-zone Offset

    The INTL extension is great for showing translated strings as well as formatting dates and numbers in a way users in different locales expect. But sometimes IntlDateFormatter outputs something that isn’t correct or throws an error. Questions about incorrect or missing time-zone offsets are showing up more often. Let’s go over why that is and how you can fix it.

    When you format a date with a given time-zone, the INTL extension calls the International Components for Unicode (ICU) library. That library contains a vast data store of formatting expectations for many different locales (a combination of a countries and/or language). It also includes a list of time-zones, their names, abbreviations, offsets and when and if they use daylight saving time (DST). Except time-zones change periodically and in some cases, quite often.

    When do time-zones change?

    While most Western time-zones tend to be quite stable with few changes, others do not. The Middle East, Central & South America, Asia and parts of Europe have seen a number of changes over the years. Locations create new time-zones, remove others, and change others if or when they observe DST.

    There is no way to predict when these changes occur. Each country and even cities or regions determine when they are going to make a change. They (or others with knowledge of the change) then submit this information to the IANA to update their time-zone database (TZDB). Throughout the year new versions are released.

    How to fix a missing or incorrect time-zone

    The issue boils down to out-of-date data. Servers want to keep their software as stable as possible. Some are running software that is a decade or more old. It is possible to update the timezone information, even without administrative access.

    Identify the version of the ICU library that is currently in use by using phpinfo() or the command line to view the installed version.

    Screen capture of a phpinfo() function call showing the INTL extension and data version.

    Running version 54 or newer will allow you to drop-in the necessary files. Otherwise it will likely be more complicated.

    Next, download the latest time-zone data files:
    https://github.com/unicode-org/icu-data/tree/main/tzdata/icunew/

    To get the right files, browse to the newest directory. It is based on the year followed by a letter. The latest versions are followed by a /44. Within that directory, there are 3 others:

    • be (big endian)
    • ee (IBM mainframes using EBCDIC character sets)
    • le (little endian)

    Choose the endianness that fits your system. The vast majority will use little endian. Within that directory will be 4 files:

    • metaZones.res
    • timezoneTypes.res
    • windowsZones.res
    • zoneinfo64.res

    Download and place those files in a directory the web application has access to read. Finally, let the ICU library know to use these files for the time-zone database resource:

    PHP
    putenv('ICU_TIMEZONE_FILES_DIR=/path/to/tzdb/directory');

    Put this script as early as possible in your application. For instance, a bootstrap file or prepend directive. Depending on your server setup, you may need to restart the application and/or web server. The timezone you were missing or time that was incorrect will be correct.

  • PHP INTL In-Depth: Convert Numbers to Roman Numerals

    PHP INTL In-Depth: Convert Numbers to Roman Numerals

    In this PHP INTL In-Depth look, you will understand how to convert integer numbers to Roman numerals (1 => I, 2 => II, 3 => III, etc). There are a few different functions floating around the web on Stack Overflow and blogs. They use an array of values to convert numbers to Roman numerals, but are not needed. Before we get to the code, we need to know what these numerals are:

    Roman Numerals? What are those?

    So what exactly are Roman numerals? They are a numeral system: a number set represented symbols. Not to be confused with number systems, which are a set of specific numbers. A number system is a more abstract idea of a set of numbers, such as real or rational numbers, while a numeral system is how a set of numbers are expressed, such as decimals or binary in computer hardware.

    Roman numerals first appeared in ancient Rome. Europe continued to use until the Middle Ages after the Roman Empire fell. Afterward, they were replaced by the Arabic numerals or Latin digits (1, 2, 3, etc) used throughout much of the world today.

    Roman numerals are still used today but are an artistic choice. Examples of where they are still used are circular analog clock faces, company marketing or advertising campaigns, and buildings or monuments with engravings of years.

    Roman numerals are read as a basic mathematical expression. Smaller numerals positioned before a larger numeral are subtracted and numbers after are added. For instance, IIV represents the number 3 where V is equal to 5 and I is equal to 1, but since they are placed before the 5 they are subtracted. Numbers of the same or lesser value positioned after a larger number are added. For instance, MMXXIII which represents the year 2023.

    INTL: NumberFormatter Decimal Numbering System

    PHP’s INTL (internationalization or i8n) library comes with a number formatter class meant to format integers & floats into human-readable numbers, currencies, and accounting. However, there is also a numbering system built into it capable of a few different features. One of those features is Roman numerals.

    Instead of creating a function or method to convert numbers to numerals, there is a C/C++ library already compiled and wrapped by PHP: The INTL library. Within that library is a NumberFormatter class which provides access to the powerful ICU Unicode internationalization formatting class.

    The formatting class is very powerful and can be used to format a number in various ways. An input is always just a plain, bland, number such as 645002. However, humans have trouble understanding large groups of numbers without something to indicate how many positions there are.

    The NumberFormatter can be used to convert that number to a more understandable 645,002. In other countries a dot may be used as the separator and formatted as 645.002. It could also be used to spell out the number as six-hundred forty-five thousand two.

    In this case, we want the representation to be a Roman numeral. So we can tell the formatter to use Roman numerals by appending to or replacing the locale with a keyword:

    PHP
    function intToRomanNumeral(int $num) {
        static $nf = new NumberFormatter('@numbers=roman', NumberFormatter::DECIMAL);
        return $nf->format($num);
    }
    

    Here are some output examples:

    PHP
    echo intToRomanNumeral(2); // II
    
    echo intToRomanNumeral(5); // V
    
    echo intToRomanNumeral(10); // X
    
    echo intToRomanNumeral(50); // L
    
    echo intToRomanNumeral(57); // LVII
    echo intToRomanNumeral(58); // LVIII
    
    echo intToRomanNumeral(100); // C
    
    echo intToRomanNumeral(150); // CL
    
    echo intToRomanNumeral(1000); // M
    
    echo intToRomanNumeral(10000); // ↂ
    

    The formatter will also format zero as N and negative numbers the same as positive numbers but preceded by a Unicode minus symbol (not the hyphen/minus symbol on keyboards). There are technically no actual zero or negative Roman numerals. They didn’t exist back when the numeral system was developed so the inclusion of them is a modern representation.

    You can alternatively use the MessageFormatter class in the same way:

    PHP
    function intToRomanNumeral(int $num) {
        static $nf = new MessageFormatter('@numbers=roman', '{0, number}');
        return $nf->format([$num]);
    }
    

    There is a caveat: performance. In my own testing, this function is about 6 times slower than NumberFormatter. You can use it for individual formats within a string, but if you are planning on using it in a loop or with many messages, it will be more performant use NumberFormatter by itself or pass a numeral converted by NumberFormatter to the MessageFormatter arguments.

    I also posted these code examples on GitLab Snippets and Stack Overflow.

  • PHP 8.2’s Subtle New Features

    PHP 8.2’s Subtle New Features

    PHP version 8.2 was released on December 8, 2022 and I’m sure you’ve already heard about the great new features. The Randomizer & random generator engines classes, DNF types; constants within traits; the sensitive parameter trait; static return types for dates; the n regular expression modifier; false, true, and null types; the readonly class modifier, a handful of deprecations, and a small performance bump.

    These features are great. They add some new things to play with, fix a few issues, and provide the usual speed boost. What about the more subtle features that aren’t reported on. They need some love too.

    phpinfo()‘s Visual Upgrade

    The first thing I will go over is a feature that doesn’t appear to be reported on anywhere. Not even in the changelog. Why?

    The phpinfo() function got a facelift. It now supports dark mode using the OS and browser’s CSS prefers-color-scheme media query. There is also now sticky headers for tables. The headers will stay at the top of the screen while you continue scrolling.

    Some other new features that go unnoticed…

    • Opimized code path for newly created files using the file:// stream wrapper.
    • Lower memory usage for strings created by various different functions.
    • Added support for curl’s CURLOPT_XFERINFOFUNCTION progress callback option. This replaces the deprecated CURLOPT_PROGRESSFUNCTION option.
    • Added support for curl’s CURLOPT_MAXFILESIZE_LARGE option which sould be used instead of CURLOPT_MAXFILESIZE in cases where the file is greater than 2 gigabytes.
    • Added curl_upkeep(callable) function that accepts a callable (function/method) and is used when HTTP2 keep-alive/ping requests are sent.
    • The idate(format, timestamp) function now supports the “N” and “o” specifiers to get the ISO day of week and ISO year respectively.
    • Added the FILTER_FLAG_GLOBAL_RANGE option when validating with the FILTER_VALIDATE_IP flag in the filter functions. The filter will fail when non-global IPV4/IPV6 IP ranges are used in accordance with RFC 6890 where the Global attribute is false.
    • A number of Apple Mac-specific memory and JIT compiler-related improvements.
    • Added JIT indirect call reduction.
    • Added the openssl_cipher_key_length(algorithm) function.
    • Added the ReflectionFunction::isAnonymous() method which returns true if the function is anonymous (e.g. a closure).
    • Added the ReflectionMethod::hasPrototype() method which returns true if the method has an inherited parent method (prototype).
    • Added a number of new Socket options.
    • Added ini_parse_quantity(string_value) function to convert parsed shorthand ini values that may include a number of supported units to integer value.
    • Added memory_reset_peak_usage() function to allow checking for excess memory usage multiple times throughout a script.

    Upgrade Soon!

    PHP 8.2 has many features that will make development and testing with PHP easier. It’s easy to overlook the more suble and less-reported-on features, but they are often some of the more useful or awaited changes. It’s highly reccomended that you upgrade to version 8.2 soon and always keep your software versions up-to-date to get the latest features and security fixes.

  • AVIF Preview Image Example

    AVIF Preview Image Example

    A common feature of web image formats is to display something – anything – while a large image is being downloaded. Progressive rendering allows the user to see that an image is being loaded and potentially start understanding the contents of the image. However, AVIF, being a video format, does not come with this feature.

    What About AVIF?

    AVIF developers debated including something similar to what I describe below, however, they tabled the idea. Including a preview image in the header would add complexity with little gain. Adding full progressive rendering this late would require a large change to the code & specification.

    Developed for low-bitrate situations, they expect images should load quickly. It’s not being properly promoted for its expected use case. I will add a more detailed article covering this in the future.

    Preview Image Example

    This example shows how an AVIF image can display a lower quality preview while the larger image is loading. CSS restrictions require the use of a parent <picture> tag. If anyone knows a way to fold this into a single <img> tag, preferably without JavaScript, please let me know.

    It uses no JavaScript for the actual image or loading portion. The script is only to add a more appealing transition effect that occurs when the large image has completed downloading. Browsers without JS will still load and display the image, but the large image will pop in instantly overtop of the inferior quality version with no transition animation.

    Other image formats (WebP/WebP2, HEIF, GIF, PNG, etc) can also use this technique, but probably wouldn’t be necessary for types that support progressive encoding/rendering (such as JPEG and JPEG XL) as they will provide a similar effect without the use of an extra image or code.

    Lets take a look at the HTML structure…

    HTML
    <picture id="avif-cats" class="block" style="background-image: url('https://assets.techie-jim.net/path-to/preview-image.avif');">
        <img class="block" width="4032" height="3024" style="display:none;" src="https://assets.techie-jim.net/path/to/preview-image.avif" decoding="async">
        <img class="block" width="4032" height="3024" onload="this.imageIsLoaded();" src="https://assets.techie-jim.net/path/to/large-image.avif" decoding="async">
    </picture>

    Wait… why are there 2 img tags? Some browsers will try to load the large image first which can block loading of the preview image until it is done. Yikes! That is not what we would want. The first one is linking to the same preview image that is set as the background of the picture tag. Note the display is also none making it invisible. This is specifically so that it will prioritize it above the larger image when loading resources.

    Take a look at the code. (It’s recommended you run the code after the DOM has finished loading [DOMContentLoaded event]):

    JavaScript
    // This part can be anywhere inside or outside your event listener.
    HTMLImageElement.prototype.imageIsLoaded = function() {
        this.parentElement.style.filter='';
        this.style.opacity=1;
    }
    
    (() => {
        let afterLoaded = function() {
            let isLoaded = false;
            let picElemCol = document.getElementsByTagName('picture');
            let imgElemCol = false;
            for(let picElem of picElemCol) {
                imgElemCol = picElem.getElementsByTagName('img');
                for(let imgElem of imgElemCol) {
                    isLoaded = imgElem.complete && imgElem.naturalHeight !== 0;
                    if(!isLoaded) {
                        picElem.style.filter = "blur(3px)";
                        imgElem.style.opacity = 0;
                    }
                }
            }
        };
    
        if(/complete|interactive|loaded/.test(document.readyState)) {
            afterLoaded();
        } else {
            document.addEventListener('DOMContentLoaded', afterLoaded, {"once": true});
        }
    })();

    The script is only used to blur the preview image until the large image has loaded, providing a more pleasing and interlaced JPEG-like appearance. Feel free to add a CSS animation to it, if you like.