spatie / laravel-medialibrary

Associate files with Eloquent models
https://spatie.be/docs/laravel-medialibrary
MIT License
5.78k stars 1.07k forks source link

Responsive images support #810

Closed brendt closed 6 years ago

brendt commented 7 years ago

I'd like to see responsive images support in the media library, more specifically srcset and sizes support. More information about this spec can be found here: https://responsiveimages.org.

Responsive images really shine when the developer doesn't have to think about manually rendering different variations of the same image. I believe the media library is the perfect platform to support this automation.

Before going into the details, this would be an example of what responsive images support might look like for a developer using the media library:

$newsItem
   ->addMedia($pathToFile)
   ->generateSrcset()
   ->withSizes($sizes)
   ->preservingOriginal()
   ->toMediaCollection();
<img 
    src="{{ $media->getUrl() }}"
    srcset="{{ $media->getSrcset() }}"
    sizes="{{ $media->getSizes() }}" />

Rendering the srcset

The srcset attribute on the <img> contains a set of URLs combined with their width in pixels. Each entry is a variation of the same image, downscaled x-amount of pixels.

Defining this variable x can be done in two ways. The developer could specify he wants eg. three variations of the image: one with a width of 1200px, one 800px and one 400px. (Those widths are just used as an example).

While this is the easiest way to define a srcset, it's not the most flexible: the goal of responsive images is to reduce bandwidth usage, while keeping as best an image as possible for the screen the user is on. The metric that matters the most is the filesize of each variation, not the width of the image.

Automating srcset widths

Instead of defining a fixed amount of variations, it's better to let the computer decide how it should downscale an image. A general rule could be: "Make x amount of variations of this image, with each variation being ± 20% smaller in filesize". In which x is calculated by the computer, instead of the developer. In practice this means calculating the width and height of the image, for which the filesize would be ± 20% smaller as the previous variation.

This can be done with the following formula:

$dimensions = [];

$ratio = $height / $width;
$area = $width * $width * $ratio;
$pixelPrice = $fileSize / $area;
$stepModifier = $fileSize * 0.2;

while ($fileSize > 0) {
    $newWidth = floor(sqrt(($fileSize / $pixelPrice) / $ratio));

    $dimensions[] = new Dimension($newWidth, $newWidth * $ratio);

    $fileSize -= $stepModifier;
}

The explanation about this simple equation can be found here: https://www.stitcher.io/blog/tackling_repsonsive_images-part_2.

srcset in the media library

Calculating and generating all variations of the srcset could potentially take a while (read: several seconds). This makes the media library with its queuing system a very good match to implement this behaviour. It could for example be possible to generate the original image on the fly, and queue the responsive variations.

Generating a srcset should be done for each media conversion, as well as the original source image. Each variation should also be run through the image optimizer.

The result is a very simple to use interface, in which the developer should not worry at all about generating responsive images.

Rendering sizes

Because of its implementation, it's impossible to automatically generate a sizes attribute for an image. The value of sizes depends on the styling context, given by CSS. sizes is a necessary pain, which allows to browser to load images in the prefetch phase, instead of waiting for the full DOM and CSS to be rendered. Only then the browser knows how wide an image is exactly. That's why the sizes attribute has to be specified; to tell the browser how wide the image will be beforehand. A more in-depth explanation about the "why" of sizes can be found here: https://ericportis.com/posts/2014/srcset-sizes

While it's not possible to completely automate sizes, it is possible to configure a collection of "reasonable presets". Eg. a configuration for images in a 4-col, 3-col, 2-col 1-col and full width. How to configure this preset should not be a concern of the media library, but it would be nice if the media library offered a method in which the sizes could be set (either as a string, or an array).

sizes:
    full-width: 100vw
    4-col: (min-width: 36em) calc(80vw / 4), (min-width: 18em) calc(80vw / 2), 80vw
    3-col: (min-width: 36em) 33.3vw, 80vw
    2-col: (min-width: 36em) 50vw, 80vw
    1-col: (min-width: 36em) 80vw

$newsItem
   // ...
   ->withSizes(config('sizes.2-col'));

One could also argue that handling the sizes should not be a concern at all for the media library, and the mapping of sizes should be done in the views. This might make more sense from the point of view of a developer. There might however be cases in which the media library is used in a CMS-like system, in which no code is written, but images still need to be configurable via the UI. I'm thinking about systems which allow you to "build your own pages" with different layouts.

Expectations

I've opened this issue to discuss this idea further with the people who are also interested. I've also been tinkering around the media library, but am not familiar enough with the code yet to get something to work without what feels like a lot of hacks. I think it's good to discuss the implementation details, and also the idea itself. I might of course have missed important things.

If we can come to a consensus on what the media library should do exactly, I'm more than willing to help implement this feature.

LasseRafn commented 6 years ago

I'd gladly work on a medialibrary wrapper for this. Seems like it wouldn't be the largest thing to solve, considering the flexibility of medialibrary.

One could create a class/helper method that generates a srcset based on the media-conversions made, and the media-conversions could potentially be generated dynamically based on the filesize formular above.

I would probably have to do a little research into responsive images.. I haven't touched them beyond in a Codepen or two for testing

freekmurze commented 6 years ago

Hi, thanks for the offer, but we’re currently implementing this feature ourselves. Should be ready in a couple of days in the v7 branch.

LasseRafn commented 6 years ago

Oh, cool! Even better 👍🏻

freekmurze commented 6 years ago

Implemented.