{"id":17024901,"url":"https://github.com/pboivin/flou","last_synced_at":"2025-04-12T11:22:29.343Z","repository":{"id":47960834,"uuid":"150134217","full_name":"pboivin/flou","owner":"pboivin","description":"PHP responsive images and lazy loading toolbox","archived":false,"fork":false,"pushed_at":"2024-02-20T16:18:13.000Z","size":3917,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-04T12:47:42.905Z","etag":null,"topics":["image","image-manipulation","images","lazyload","lqip","php","responsive"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pboivin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2018-09-24T16:33:40.000Z","updated_at":"2025-02-26T08:43:33.000Z","dependencies_parsed_at":"2024-02-20T17:45:33.157Z","dependency_job_id":null,"html_url":"https://github.com/pboivin/flou","commit_stats":{"total_commits":360,"total_committers":1,"mean_commits":360.0,"dds":0.0,"last_synced_commit":"d136fa7ef81f1647e05ae58264377d5ef62ff5ae"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pboivin%2Fflou","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pboivin%2Fflou/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pboivin%2Fflou/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pboivin%2Fflou/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pboivin","download_url":"https://codeload.github.com/pboivin/flou/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248558319,"owners_count":21124256,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["image","image-manipulation","images","lazyload","lqip","php","responsive"],"created_at":"2024-10-14T07:27:22.072Z","updated_at":"2025-04-12T11:22:29.317Z","avatar_url":"https://github.com/pboivin.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# flou\n\n\u003cp\u003e\n\u003ca href=\"https://github.com/pboivin/flou/actions\"\u003e\u003cimg src=\"https://github.com/pboivin/flou/workflows/tests/badge.svg\" alt=\"Build Status\"\u003e\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/pboivin/flou\"\u003e\u003cimg src=\"https://img.shields.io/packagist/v/pboivin/flou\" alt=\"Latest Stable Version\"\u003e\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/pboivin/flou\"\u003e\u003cimg src=\"https://img.shields.io/packagist/l/pboivin/flou\" alt=\"License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\nFlou is a PHP package integrating [Glide (PHP)](https://github.com/thephpleague/glide) and [vanilla-lazyload (JS)](https://github.com/verlok/vanilla-lazyload). It's optimized to quickly implement lazy loading and responsive images from a local folder of source images.\n\n**Features:**\n\n- Transform local images on initial page load (does not expose Glide URLs)\n- Can leverage custom Glide configurations (e.g. source images on S3)\n- Generate responsive HTML for `img` and `picture` elements\n- Useable with static site generators and in CLI scripts\n- Framework agnostic (a set of plain PHP classes)\n\n**Requirements:**\n\n- PHP \u003e= 8.0\n\n**Table of contents:**\n\n- [Installing](#installing)\n- [Getting Started](#getting-started)\n- [Image Transformations](#image-transformations)\n- [Image Rendering](#image-rendering)\n- [Image Sets (Responsive Images)](#image-sets-responsive-images)\n- [Remote Images](#remote-images)\n- [Examples](#examples)\n- [Development](#development)\n- [License](#license)\n\n**Demo project:**\n\nSee the [flou-jigsaw-demo](https://github.com/pboivin/flou-jigsaw-demo) repository for an example project integrating Flou with the [Jigsaw](https://github.com/tighten/jigsaw) PHP static site generator.\n\n## Installing \n\nThe package can be installed via Composer:\n\n```\ncomposer require pboivin/flou\n```\n\nThis also installs Glide as a Composer dependency.\n\nYou can pull in the vanilla-lazyload library via a CDN:\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.min.js\"\u003e\u003c/script\u003e\n```\n\nor via NPM:\n\n```\nnpm install --save vanilla-lazyload\n```\n\nConsult the [vanilla-lazyload documentation](https://github.com/verlok/vanilla-lazyload#-getting-started---script) for more installation options.\n\n\n## Getting Started\n\nFirst, initialize the `LazyLoad` JS object. Add the following script to your page template:\n\n```html\n\u003cscript\u003e\n    document.addEventListener(\"DOMContentLoaded\", () =\u003e {\n        new LazyLoad({\n            elements_selector: \".lazyload\",\n        });\n    });\n\u003c/script\u003e\n```\n\nThen, initialize the `ImageFactory` PHP object with your project-specific configuration:\n\n```php\nuse Pboivin\\Flou\\ImageFactory;\n\n$flou = new ImageFactory([\n    'sourcePath' =\u003e '/home/user/my-site.com/public/images/source',\n    'cachePath' =\u003e '/home/user/my-site.com/public/images/cache',\n    'sourceUrlBase' =\u003e '/images/source',\n    'cacheUrlBase' =\u003e '/images/cache',\n]);\n```\n\n\n#### Configuration\n\nThe required options are:\n\n| Name | Type | Description |\n|---|---|---|\n| `sourcePath` | string | The full path to the source images. |\n| `cachePath` | string | The full path where Glide will store the image transformations. |\n| `sourceUrlBase` | string | The base URL for the source images. |\n| `cacheUrlBase` | string | The base URL for the transformed images. |\n\nOther options:\n\n| Name | Type | Description |\n|---|---|---|\n| `glideParams` | array | [Default Glide parameters for LQIP elements.](#default-glide-parameters) |\n| `renderOptions` | array | [Default render options for all images.](#default-render-options) |\n\n\n#### Framework Integration\n\nIf you're using a framework with a Service Container, you can register the `$flou` instance as a singleton for your entire application. This will be your entry point to transform and render images.\n\n\n#### Extra JS and CSS\n\nSome examples below require additional JS and CSS. You'll find a more complete sample in the [assets directory](./assets).\n\n\n## Image Transformations\n\n\n#### Transforming source images\n\nUse the `image()` method to transform a single image into a low-quality image placeholder (LQIP):\n\n```php\n$image = $flou-\u003eimage('01.jpg');\n```\n\nYou can also provide custom Glide parameters for the image transformation:\n\n```php\n$image = $flou-\u003eimage('01.jpg', [\n    'w' =\u003e 10,\n    'h' =\u003e 10,\n    'fit' =\u003e 'crop',\n]);\n```\n\nYou'll find all available parameters in the [Glide documentation](https://glide.thephpleague.com/2.0/api/quick-reference/).\n\nAs you can see, the default parameters are used to generate LQIP from source images, but you are not restricted to that. You may generate as many transformations as you need from the source image:\n\n```php\n$phone = $flou-\u003eimage('01.jpg', ['w' =\u003e 500]);\n$tablet = $flou-\u003eimage('01.jpg', ['w' =\u003e 900]);\n$desktop = $flou-\u003eimage('01.jpg', ['w' =\u003e 1300]);\n```\n\nIf you're working with responsive images and the `srcset` attribute, have a look at the next section ([Image Sets](#image-sets-responsive-images)).\n\n\n#### Default Glide parameters\n\nYou can customize the default Glide parameters in the `ImageFactory` configuration:\n\n```php\n$flou = new ImageFactory([\n    // ...\n    'glideParams' =\u003e [\n        'h' =\u003e 10,\n        'fm' =\u003e 'gif',\n    ],\n]);\n```\n\n\n#### Image objects\n\nThe `image()` method returns an `Image` object, from which you can conveniently access the source image file and the transformed (cached) image file:\n\n```php \n$image = $flou-\u003eimage('01.jpg');\n\n# Source image data:\necho $image-\u003esource()-\u003eurl();       # /images/source/01.jpg\necho $image-\u003esource()-\u003epath();      # /home/user/my-site.com/public/images/source/01.jpg\necho $image-\u003esource()-\u003ewidth();     # 3840 \necho $image-\u003esource()-\u003eheight();    # 2160\necho $image-\u003esource()-\u003eratio();     # 1.77777778\n\n# Transformed image data:\necho $image-\u003ecached()-\u003eurl();       # /images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.jpg\n...\n```\n\nUse the `toArray()` method to export the image to a plain array:\n\n```php\n$data = $image-\u003etoArray();\n\n# [\n#     \"source\" =\u003e [\n#         \"url\" =\u003e \"/images/source/01.jpg\",\n#         \"path\" =\u003e \"/home/user/my-site.com/public/images/source/01.jpg\",\n#         \"width\" =\u003e 3840,\n#         \"height\" =\u003e 2160,\n#         \"ratio\" =\u003e 1.77777778,\n#     ],\n#     \"cached\" =\u003e [\n#         \"url\" =\u003e \"/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.jpg\",\n#         ...\n#     ],\n# ]\n```\n\n\n#### Image resampling\n\nImage resampling is a simple way to reuse a transformed image as the source of another transformation. Use the `resample()` method to begin:\n\n```php\n$greyscale = $flou-\u003eresample('01.jpg', [\n    'filt' =\u003e 'greyscale',\n    'w' =\u003e 2000,\n]);\n```\n\nThis is the same as calling `image()`, but returns an instance of `ResampledImage` instead. The resampled image can then be used again as a source for `image()`:\n\n```php\n$image = $flou-\u003eimage($greyscale, ['w' =\u003e 50]);\n\n# Source image:\necho $image-\u003esource()-\u003eurl();       # /images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg\necho $image-\u003esource()-\u003ewidth();     # 2000\n...\n\n# Transformed image:\necho $image-\u003ecached()-\u003eurl();       # /images/cache/_r/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg/9a5bdd58bbc27a556121925569af7b0c.jpg\necho $image-\u003ecached()-\u003ewidth();     # 50\n...\n```\n\n\n## Image Rendering\n\n\n#### Rendering single images\n\nThe `render()` method on the image returns an `ImageRender` object, which prepares HTML suitable for the vanilla-lazyload library. Then, `img()` is used to render an `img` element:\n\n```php\n$image = $flou-\u003eimage('01.jpg');\n\necho $image\n        -\u003erender()\n        -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eSee HTML Output\u003c/summary\u003e\n\n```html\n\u003cimg \n  class=\"lazyload w-full\" \n  alt=\"Lorem ipsum\" \n  src=\"/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif\" \n  data-src=\"/images/source/01.jpg\" \n  width=\"3840\" \n  height=\"2160\"\n\u003e\n```\n\u003c/details\u003e\n\u003cbr\u003e\n\nOptions passed into `img()` are included as HTML attributes on the element. Attribute values are not escaped by default.\n\nSome attributes are automatically generated (e.g. `src`, `width`, `height`, etc.). You can override them with a `!` prefix:\n\n```php\necho $image\n        -\u003erender()\n        -\u003eimg([\n            'class' =\u003e 'w-full',\n            'alt' =\u003e 'Lorem ipsum',\n            '!src' =\u003e false,\n        ]);\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eSee HTML Output\u003c/summary\u003e\n\n```html\n\u003cimg \n  class=\"lazyload w-full\" \n  alt=\"Lorem ipsum\" \n  data-src=\"/images/source/01.jpg\" \n  width=\"3840\" \n  height=\"2160\" \n\u003e\n```\n\u003c/details\u003e\n\u003cbr\u003e\n\n\n\u003ca name=\"image-render-configuration\"\u003e\u003c/a\u003e\n#### Render options\n\nThe `ImageRender` object can be configured to optimize the HTML output:\n\n- **`useAspectRatio()`:** Prevents content shifting when the LQIP is replaced with the source image:\n\n    ```php\n    echo $image\n            -\u003erender()\n            -\u003euseAspectRatio()\n            -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n\n    # or use a custom aspect-ratio:\n\n    echo $image\n            -\u003erender()\n            -\u003euseAspectRatio(16 / 9)\n            -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n    ```\n\n    \u003cdetails\u003e\n    \u003csummary\u003eSee HTML Output\u003c/summary\u003e\n    \n    ```html\n    \u003cimg \n      class=\"lazyload w-full\" \n      alt=\"Lorem ipsum\" \n      style=\"aspect-ratio: 1.77777778; object-fit: cover; object-position: center;\" \n      ...\n    \u003e\n    ```\n    \u003c/details\u003e\n    \u003cbr\u003e\n\n- **`usePaddingTop()`:** A workaround for older browsers not supporting the `aspect-ratio` CSS property:\n\n    ```php\n    echo $image\n            -\u003erender()\n            -\u003eusePaddingTop()\n            -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n\n    # or use a custom aspect-ratio:\n\n    echo $image\n            -\u003erender()\n            -\u003eusePaddingTop(16 / 9)\n            -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n    ```\n\n    \u003cdetails\u003e\n    \u003csummary\u003eSee HTML Output\u003c/summary\u003e\n    \n    ```html\n    \u003cdiv class=\"lazyload-padding\" style=\"position: relative; padding-top: 56.25%;\"\u003e\n      \u003cimg\n        class=\"lazyload w-full\"\n        alt=\"Lorem ipsum\" \n        style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; \n               object-fit: cover; object-position: center;\"\n        ...\n      \u003e\n    \u003c/div\u003e\n    ```\n    \u003c/details\u003e\n    \u003cbr\u003e\n\n- **`useWrapper()`:** Wraps the image with an extra `div` and separates the LQIP element from the main `img` element. This is used to add a fade-in effect when the image is loaded.\n\n    (Requires additional JS and CSS. [See fade-in example.](#fade-in-image-on-load))\n\n    ```php\n    echo $image\n            -\u003erender()\n            -\u003euseWrapper()\n            -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n    ```\n\n\n    \u003cdetails\u003e\n    \u003csummary\u003eSee HTML Output\u003c/summary\u003e\n    \n    ```html\n    \u003cdiv class=\"lazyload-wrapper\"\u003e\n      \u003cimg \n        class=\"lazyload w-full\" \n        alt=\"Lorem ipsum\" \n        ...\n      \u003e\n      \u003cimg \n        class=\"lazyload-lqip\" \n        src=\"/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif\"\n      \u003e\n    \u003c/div\u003e\n    ```\n    \u003c/details\u003e\n    \u003cbr\u003e\n\n- **`useBase64Lqip()`:** Inlines a Base64 version of the LQIP in the `src` attribute of the `img` element. This reduces the number of HTTP requests needed to display a page, at the cost of making the HTML a bit heavier.\n\n    ```php\n    echo $image\n            -\u003erender()\n            -\u003euseBase64Lqip()\n            -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n    ```\n\n    \u003cdetails\u003e\n    \u003csummary\u003eSee HTML Output\u003c/summary\u003e\n    \n    ```html\n    \u003cimg \n      class=\"lazyload w-full\" \n      alt=\"Lorem ipsum\" \n      src=\"data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH+EUNyZWF0Z...\"\n      data-src=\"/images/source/01.jpg\" \n      width=\"2932\" \n      height=\"2000\"\n    \u003e\n    ```\n    \u003c/details\u003e\n    \u003cbr\u003e\n\n\n#### Default render options\n\nYou can set the default render options for all images in the `ImageFactory` configuration:\n\n```php\n$flou = new ImageFactory([\n    // ...\n    'renderOptions' =\u003e [\n        'aspectRatio' =\u003e true,\n        'wrapper' =\u003e true,\n        'base64Lqip' =\u003e true,\n        // ...\n    ],\n]);\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eSee Available Options\u003c/summary\u003e\n\n| Name | Type | Description |\n|---|---|---|\n| `baseClass` | string | CSS class for `img` element. Default: `'lazyload'` |\n| `wrapperClass` | string | CSS class for wrapper element. Default: `'lazyload-wrapper'` |\n| `lqipClass` | string | CSS class for LQIP element. Default: `'lazyload-lqip'` |\n| `paddingClass` | string | CSS class for padding-specific wrapper element. Default: `'lazyload-padding'` |\n| `aspectRatio` | boolean or number | Use aspect ratio. Default: `false` |\n| `paddingTop` | boolean or number | Use padding-top workaround. Default: `false` |\n| `wrapper` | boolean | Use wrapper element. Default: `false` |\n| `base64Lqip` | boolean | Use Base64 LQIP value. Default: `false` |\n\u003c/details\u003e\n\u003cbr\u003e\n\n\n#### Noscript variation\n\nUse the `noScript()` method to render an `img` element without any lazy loading behavior:\n\n```php\necho $image\n        -\u003erender()\n        -\u003enoScript(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eSee HTML Output\u003c/summary\u003e\n\n```html\n\u003cimg \n  class=\"lazyload-noscript w-full\" \n  alt=\"Lorem ipsum\" \n  src=\"/images/source/01.jpg\" \n  width=\"2932\" \n  height=\"2000\"\n\u003e\n```\n\u003c/details\u003e\n\u003cbr\u003e\n\nThis is used to add a `noscript` image fallback. ([See Noscript image fallback example](#noscript-fallback))\n\nIt can also be used creatively to work on the source image with CSS classes and HTML attributes. ([See Browser native lazy loading example](#native-lazy-loading-example))\n\n\n\u003cspan id=\"working-with-image-sets-responsive-images\"\u003e\u003c/span\u003e\n\n## Image Sets (Responsive Images)\n\n\n#### Single source (`img` element)\n\nUse the `imageSet()` method to transform a source image into a set of responsive images:\n\n```php\n$imageSet = $flou-\u003eimageSet([\n    'image' =\u003e '01.jpg',\n    'sizes' =\u003e '(max-width: 500px) 100vw, 50vw',\n    'widths' =\u003e [500, 900, 1300, 1700],\n]);\n```\n\nThis returns an `ImageSet` object, which prepares all variations of the source image. The `render()` method on the image set returns an `ImageSetRender` instance, as seen before with single images:\n\n```php\necho $imageSet\n        -\u003erender()\n        -\u003euseAspectRatio()\n        -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eSee HTML Output\u003c/summary\u003e\n\n```html\n\u003cimg \n  class=\"lazyload w-full\" \n  alt=\"Lorem ipsum\" \n  style=\"aspect-ratio: 1.77777778; object-fit: cover; object-position: center;\" \n  src=\"/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif\" \n  data-src=\"/images/cache/01.jpg/b8648e93b40b56d5c5a78acc7a23e3d9.jpg\" \n  data-srcset=\"/images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 500w, \n               /images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg 900w, \n               /images/cache/01.jpg/1eac615f1a50f20c434e5944225bdd4f.jpg 1300w, \n               /images/cache/01.jpg/b8648e93b40b56d5c5a78acc7a23e3d9.jpg 1700w\" \n  data-sizes=\"(max-width: 500px) 100vw, 50vw\"\n  width=\"1700\" \n  height=\"956\" \n\u003e\n```\n\u003c/details\u003e\n\u003cbr\u003e\n\nLike `ImageRender`, you can optimize `ImageSetRender` with the [same methods](#image-render-configuration):\n\n- `useAspectRatio()`\n- `usePaddingTop()`\n- `useWrapper()`\n- `useBase64Lqip()`\n- `noScript()`\n\n\n#### Multiple sources (`picture` element)\n\nWith a similar configuration, `imageSet()` also handles multiple source images:\n\n```php\n$imageSet = $flou-\u003eimageSet([\n    [\n        'image' =\u003e 'portrait.jpg',\n        'media' =\u003e '(max-width: 1023px)',\n        'sizes' =\u003e '100vw',\n        'widths' =\u003e [400, 800, 1200],\n    ],\n    [\n        'image' =\u003e 'landscape.jpg',\n        'media' =\u003e '(min-width: 1024px)',\n        'sizes' =\u003e '66vw',\n        'widths' =\u003e [800, 1200, 1600],\n    ],\n]);\n```\n\nThen, the `picture()` method is used to render a `picture` element:\n\n```php\necho $imageSet\n        -\u003erender()\n        -\u003epicture(['class' =\u003e 'my-image', 'alt' =\u003e 'Lorem ipsum']);\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eSee HTML Output\u003c/summary\u003e\n\n```html\n\u003cpicture\u003e\n  \u003csource\n    media=\"(max-width: 1023px)\"\n    data-sizes=\"100vw\"\n    data-srcset=\"/images/cache/portrait.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 400w,\n                 /images/cache/portrait.jpg/1422c06dea2257858f6437b9675fba1c.jpg 800w,\n                 /images/cache/portrait.jpg/de828e8798017be816f79e131e41dcc9.jpg 1200w\"\n  \u003e\n  \u003csource \n    media=\"(min-width: 1024px)\" \n    data-sizes=\"66vw\" \n    data-srcset=\"/images/cache/landscape.jpg/c6f9c52bea237b64cc98fc9f5f3f15c6.jpg 800w,\n                 /images/cache/landscape.jpg/fcc882305b523e823c7a24df05045c5a.jpg 1200w,\n                 /images/cache/landscape.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 1600w\"\n  \u003e\n  \u003cimg\n    class=\"lazyload my-image\"\n    alt=\"Lorem ipsum\"\n    src=\"/images/cache/landscape.jpg/66d1d4a938d99f2b0234e08008af09a8.gif\"\n    data-src=\"/images/cache/landscape.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg\"\n    width=\"1600\"\n    height=\"900\"\n  \u003e\n\u003c/picture\u003e\n```\n\u003c/details\u003e\n\u003cbr\u003e\n\nSee also: [Art-directed `picture` element example](#art-directed-picture-element)\n\n\n#### Multiple formats (`picture` element)\n\nThe configuration allows multiple image formats for each source:\n\n```php\n$imageSet = $flou-\u003eimageSet([\n    'image' =\u003e '01.jpg',\n    'sizes' =\u003e '100vw',\n    'widths' =\u003e [400, 800, 1200, 1600],\n    'formats' =\u003e ['webp', 'jpg'],\n]);\n\necho $imageSet\n        -\u003erender()\n        -\u003epicture(['class' =\u003e 'my-image', 'alt' =\u003e 'Lorem ipsum']);\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eSee HTML Output\u003c/summary\u003e\n\n```html\n\u003cpicture\u003e\n  \u003csource\n    type=\"image/webp\"\n    data-srcset=\"/images/cache/01.jpg/7c7086baa60bb4b3876b14dd577fa9e8.webp 400w,\n                 /images/cache/01.jpg/49ada5db20e72d539b611e5d17640d2f.webp 800w,\n                 /images/cache/01.jpg/cb48c273b44bd0f00155d4932231fe28.webp 1200w,\n                 /images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.webp 1600w\"\n    data-sizes=\"100vw\"\n  \u003e\n  \u003csource\n    type=\"image/jpeg\"\n    data-srcset=\"/images/cache/01.jpg/27e8a3f7fb4abe60654117a34f2007e1.jpg 400w,\n                 /images/cache/01.jpg/f319ea155d0009a7e842f50fcc020fe3.jpg 800w,\n                 /images/cache/01.jpg/cfdad3b69ae3a15ba479aa85868e75f3.jpg 1200w,\n                 /images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg 1600w\"\n    data-sizes=\"100vw\"\n  \u003e\n  \u003cimg\n    class=\"lazyload w-full\"\n    alt=\"Lorem ipsum\"\n    src=\"/images/cache/01.jpg/bd0dc309cfc3b71731b2e2df3d6e130b.gif\"\n    data-src=\"/images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg\"\n    width=\"1600\"\n    height=\"900\"\n  \u003e\n\u003c/picture\u003e\n```\n\u003c/details\u003e\n\u003cbr\u003e\n\n\n#### Custom Glide parameters\n\nYou can provide an array of base Glide parameters as a second argument to `imageSet()`:\n\n```php\n$imageSet = $flou-\u003eimageSet([\n    'image' =\u003e '01.jpg',\n    'sizes' =\u003e '100vw',\n    'widths' =\u003e [400, 800, 1200, 1600],\n    'formats' =\u003e ['webp', 'jpg'],\n], [\n    'q' =\u003e 80,\n]);\n```\n\nYou'll find all available parameters in the [Glide documentation](https://glide.thephpleague.com/2.0/api/quick-reference/).\n\nNote: You may use all parameters except `w` and `fm`, which are automatically generated from the `widths` and `formats` configuration above.\n\n\n## Remote Images\n\nThe base `ImageFactory` class is optimized for cases where both source and cached images exist on the local filesystem. The `RemoteImageFactory` class was introduced to enable new use-cases:\n\n- Working with remote Glide endpoints\n- Integrating with existing Glide Server configurations\n\nThis adds support for images stored on remote filesystems, such as Amazon S3.\n\n\n#### Configuration\n\nThe options for `RemoteImageFactory` are:\n\n| Name | Type | Description |\n|---|---|---|\n| `glideServer` | League\\Glide\\Server | A `Server` instance. |\n| `glideUrlBase` | string | Alternatively, the base URL for a remotely accessible Glide server. |\n| `glideUrlSignKey` | string | Private key used for Glide HTTP signatures. (optional) |\n\n\n#### Glide Endpoint\n\nIf you already have a Glide instance setup and publicly accessible, you can hook into it with the following configuration:\n\n```php\n$flou = new Pboivin\\Flou\\RemoteImageFactory([\n    'glideUrlBase' =\u003e '/glide', // or use a full URL: https://cdn.my-site.com/glide\n    'glideUrlSignKey' =\u003e 'secret',\n]);\n\n$image = $flou-\u003eimage('test.jpg');\n\n//...\n```\n\n\n#### Glide Server\n\nAlternatively, you can pass in a fully configured Glide `Server` object:\n\n```php\n// @see https://flysystem.thephpleague.com/docs/adapter/aws-s3-v3/\n\n$sourceFilesystem = new League\\Flysystem\\Filesystem(\n    new League\\Flysystem\\AwsS3V3\\AwsS3V3Adapter(/* S3 adapter configuration */);\n);\n\n$server = League\\Glide\\ServerFactory::create([\n    'source' =\u003e $sourceFilesystem,\n    'cache' =\u003e '/home/my-site.com/storage/glide-cache',\n    'base_url' =\u003e '/glide',\n]);\n\n$flou = new Pboivin\\Flou\\RemoteImageFactory([\n    'glideServer' =\u003e $server,\n    'glideUrlSignKey' =\u003e 'secret',\n]);\n\n$image = $flou-\u003eimage('test.jpg');\n\n//...\n```\n\nIf you're using Laravel, you can access the filesystem driver from the `Storage` facade:\n\n```php\n// @see https://laravel.com/docs/filesystem\n\n$sourceFilesystem = Illuminate\\Support\\Facades\\Storage::disk('s3')-\u003egetDriver(),\n\n//...\n```\n\n\n#### Caveats\n\nWhen using `RemoteImageFactory`, it is too costly to fetch remote images to analyze their dimensions. Therefore, rendered images will not include `width` and `height` attributes. I recommend leveraging `useAspectRatio()` with a fixed aspect ratio value if possible.\n\nSimilarly, `useBase64Lqip()` will return a blank placeholder instead of a Base64 encoded LQIP.\n\n[Image resampling](#image-resampling) is not available for remote images.\n\n\n## Examples\n\n\n#### Fade-in image on load\n\n*Extra JS and CSS:*\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n    /**\n     * vanilla-lazyload API reference: \n     * https://github.com/verlok/vanilla-lazyload#options\n     */\n\n    document.addEventListener(\"DOMContentLoaded\", () =\u003e {\n        new LazyLoad({\n            elements_selector: \".lazyload\",\n\n            callback_loaded: (el) =\u003e {\n                const wrapper = el.closest(\".lazyload-wrapper\");\n                if (wrapper) {\n                    wrapper.classList.add(\"loaded\");\n                }\n            }\n        });\n    });\n\u003c/script\u003e\n\n\u003cstyle\u003e\n    /* Example styles — adjust to taste */\n\n    .lazyload-wrapper {\n        position: relative;\n        overflow: hidden;\n    }\n\n    .lazyload-wrapper .lazyload-lqip {\n        filter: blur(10px);\n        transform: scale(1.1);\n        position: absolute;\n        top: 0;\n        left: 0;\n        width: 100%;\n        height: 100%;\n    }\n\n    .lazyload-wrapper.loaded .lazyload-lqip {\n        opacity: 0;\n        transition: opacity 0.5s;\n        transition-delay: 0.5s;\n    }\n\u003c/style\u003e\n```\n\n*Usage:*\n\n```php\n\u003c?= $flou\n        -\u003eimage('01.jpg')\n        -\u003erender()\n        -\u003euseAspectRatio()\n        -\u003euseWrapper()\n        -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum']);\n?\u003e\n```\n\n\u003chr\u003e\n\n#### Art-directed `picture` element\n\n*CSS:*\n\n```html\n\u003cstyle\u003e\n    .my-image {\n        width: 100%;\n        height: auto;\n        aspect-ratio: calc(3 / 4);\n        object-fit: cover;\n        object-position: center;\n    }\n\n    @media screen and (min-width: 1024px) {\n        .my-image {\n            max-width: 66vw;\n            aspect-ratio: calc(16 / 9);\n        }\n    }\n\u003c/style\u003e\n```\n\n*Usage:*\n\n```php\n\u003c?= $flou-\u003eimageSet([\n        [\n            'image' =\u003e 'portrait.jpg',\n            'media' =\u003e '(max-width: 1023px)',\n            'sizes' =\u003e '100vw',\n            'widths' =\u003e [400, 800, 1200],\n        ],\n        [\n            'image' =\u003e 'landscape.jpg',\n            'media' =\u003e '(min-width: 1024px)',\n            'sizes' =\u003e '66vw',\n            'widths' =\u003e [800, 1200, 1600],\n        ],\n    ])\n    -\u003erender()\n    -\u003epicture(['class' =\u003e 'my-image', 'alt' =\u003e 'Lorem ipsum']);\n?\u003e\n```\n\n\u003chr\u003e\n\n#### Noscript fallback\n\n*CSS:*\n\n```html\n\u003cnoscript\u003e\n    \u003cstyle\u003e\n        .lazyload {\n            display: none;\n        }\n    \u003c/style\u003e\n\u003c/noscript\u003e\n```\n\n*Usage:*\n\n```php\n\u003cdiv\u003e\n    \u003c?= ($image = $flou-\u003eimage('01.jpg'))\n            -\u003erender()\n            -\u003eimg(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum'])\n    ?\u003e\n    \u003cnoscript\u003e\n        \u003c?= $image\n                -\u003erender()\n                -\u003enoScript(['class' =\u003e 'w-full', 'alt' =\u003e 'Lorem ipsum'])\n        ?\u003e\n    \u003c/noscript\u003e\n\u003c/div\u003e\n```\n\n\u003chr\u003e\n\n\u003ca name=\"native-lazy-loading-example\"\u003e\u003c/a\u003e\n#### Native lazy loading (no JS, with LQIP)\n\n*Usage:*\n\n```php\n\u003c?= ($image = $flou-\u003eimage('01.jpg'))\n        -\u003erender()\n        -\u003euseAspectRatio()\n        -\u003enoScript([\n            'class' =\u003e 'w-full', \n            'alt' =\u003e 'Lorem ipsum',\n            'loading' =\u003e 'lazy',\n            'decoding' =\u003e 'async',\n            'style' =\u003e \"background-image: url({$image-\u003ecached()-\u003eurl()});\n                        background-size: cover;\"\n        ]);\n?\u003e\n```\n\n\u003chr\u003e\n\n#### Lazy loaded background image\n\n*Usage:*\n\n```php\n\u003c?php $image = $flou-\u003eimage('01.jpg'); ?\u003e\n\n\u003cdiv class=\"lazyload\"\n     data-bg=\"\u003c?= $image-\u003esource()-\u003eurl() ?\u003e\"\n     style=\"background-image: url( \u003c?= $image-\u003ecached()-\u003eurl() ?\u003e );\n            background-size: cover;\"\n\u003e\n    \u003c!-- ... --\u003e\n\u003c/div\u003e\n```\n\n\u003chr\u003e\n\n#### CLI script\n\n*Preprocess all images in a source directory and prepare a JSON inventory file:*\n\n```php\n\u003c?php\n\nrequire 'vendor/autoload.php';\n\n$flou = new Pboivin\\Flou\\ImageFactory([\n    'sourcePath' =\u003e './public/images/source',\n    'cachePath' =\u003e './public/images/cache',\n    'sourceUrlBase' =\u003e '/images/source',\n    'cacheUrlBase' =\u003e '/images/cache',\n]);\n\n$data = [];\n\nforeach (glob('./public/images/source/*.jpg') as $path) {\n    $file = basename($path);\n\n    echo \"Processing image: $file\\n\";\n\n    $data[$file] = [\n        'source' =\u003e $flou-\u003eimage($file)-\u003esource()-\u003etoArray(),\n        'lqip' =\u003e $flou-\u003eimage($file)-\u003ecached()-\u003etoArray(),\n        'responsive' =\u003e array_map(\n            fn ($width) =\u003e $flou-\u003eimage($file, ['w' =\u003e $width])-\u003ecached()-\u003etoArray(),\n            [500, 900, 1300, 1700]\n        ),\n    ];\n}\n\nfile_put_contents('./data/images.json', json_encode($data, JSON_PRETTY_PRINT));\n\necho \"Done!\\n\";\n```\n\n\n## Development\n\n#### Test suite ([phpunit](https://phpunit.de/))\n\n```\ncomposer run test\n```\n\n#### Static analysis ([phpstan](https://phpstan.org/))\n\n```\ncomposer run analyse\n```\n\n#### Code formatting ([pint](https://laravel.com/docs/9.x/pint))\n\n```\ncomposer run format\n```\n\n\n## License\n\nFlou is open-sourced software licensed under the [MIT license](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpboivin%2Fflou","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpboivin%2Fflou","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpboivin%2Fflou/lists"}