Storehouse.Responsive (Storehouse v0.2.0)

Copy Markdown View Source

What a page needs to show a photograph at every screen's size, from a preset of variants with a ladder of widths (Storehouse.Variants): its src, its srcset, its size, its placeholder. As data, for a JSON API, or as an <img> (Storehouse.Components.img/1).

Storehouse.Responsive.resolve(file, :photo, sizes: "(min-width: 60rem) 60rem, 100vw")
#=> %Storehouse.Responsive{
#     src: "/variants/…/photo-2560-e04b77d1",
#     srcset: "/variants/…/photo-640-… 640w, …, /variants/…/photo-2560-… 2400w",
#     sizes: "(min-width: 60rem) 60rem, 100vw",
#     width: 2400, height: 1600,
#     placeholder: "data:image/webp;base64,…", color: "#9a7b76", …}

The srcset has the ladder's widths below the original's, then the original's own (up to the widest step): never a size larger than the photograph. Each is asked for at its step of the ladder, or the next one up, so every photograph's sizes are the ladder's, and a CDN keeps few of them; a step past the original's width gives the original's. Its width and height are those of the largest, for the page to keep the room before the image arrives.

It reads no store: the original's width and height are the file's (Storehouse.store/2 keeps them), its placeholder and colour its analysis's (Storehouse.Analyzer.Placeholder, ThumbHash, BlurHash). It encodes as JSON (JSON.Encoder, Jason.Encoder).

Warming

A variant is made the first time it is asked for, and a large original takes seconds: warm/3 asks for each size once, before the first visitor does, and warm_later/3 does it out of the request, on the app's runner. Off unless the app turns it on, in production:

config :storehouse, Storehouse.Responsive, warm: true

Summary

Functions

Whether warming is on: :enabled, else the configured :warm, false by default, so that development and tests ask no image service.

The src, srcset, size and placeholder of file with preset.

Every URL resolve/3 gives for file with preset, once each: the sizes to warm.

Makes file's sizes with each of presets before a visitor asks for them: {:ok, count}, or {:error, {:not_warmed, failed}} with what was not made and why.

warm/3 out of the request, as a job of the app's runner, when warming is on (enabled?/1), and nothing when it is off. The original is not read: the file's width is all it needs. A file's job waiting is not queued again.

The widths of file's srcset with preset, each with the step of the ladder it is asked for at: [{width, step}], narrowest first.

Types

t()

@type t() :: %Storehouse.Responsive{
  blurhash: String.t() | nil,
  color: String.t() | nil,
  height: pos_integer() | nil,
  placeholder: String.t() | nil,
  sizes: String.t() | nil,
  src: String.t(),
  srcset: String.t(),
  thumbhash: String.t() | nil,
  width: pos_integer() | nil
}

Functions

enabled?(opts \\ [])

@spec enabled?(keyword()) :: boolean()

Whether warming is on: :enabled, else the configured :warm, false by default, so that development and tests ask no image service.

resolve(file, preset, opts \\ [])

@spec resolve(Storehouse.File.t(), atom(), keyword()) :: t()

The src, srcset, size and placeholder of file with preset.

Options

  • :src - the width of src, for a browser that takes no srcset (the step of the ladder as wide, or the next one up). The default is the largest.
  • :sizes - the sizes attribute, as given: what the layout's slot for the photograph is, at each screen's width. See the Variants guide.
  • Storehouse.Variants.url/3's, for each URL.

urls(file, preset, opts \\ [])

@spec urls(Storehouse.File.t(), atom(), keyword()) :: [String.t()]

Every URL resolve/3 gives for file with preset, once each: the sizes to warm.

Options

  • :up_to - the widest step: larger ones are left to be made when asked for (a lightbox's, zoomed in). The default is every one.
  • :src, and Storehouse.Variants.url/3's, as for resolve/3.

warm(file, presets, opts \\ [])

@spec warm(Storehouse.File.t(), atom() | [atom()], keyword()) ::
  {:ok, non_neg_integer()} | {:error, {:not_warmed, [{term(), term()}]}}

Makes file's sizes with each of presets before a visitor asks for them: {:ok, count}, or {:error, {:not_warmed, failed}} with what was not made and why.

With a renderer that renders in the app (Storehouse.Renderer.Vix), each is made with Storehouse.variant/3, in the format a browser of today is given. With one of URLs (Storehouse.Renderer.Imgproxy), each URL is asked for, as a browser of today asks: the service renders it and the CDN in front keeps it. One at a time, so the app's visitors never wait behind a batch.

It warms when it is called, turned on or not.

Options

  • :up_to and :src - as for urls/2.
  • :through - :render or :http. The default is the renderer's: :http asks a variant's URL, through the plug or a CDN, for a renderer in the app that is behind one.
  • :accept - the Accept header. The default is a browser's that takes AVIF and WebP.
  • :base_url - put before a URL that is a path, to ask it over HTTP: "https://example.com".
  • :timeout - for each, in milliseconds. The default is 90 seconds.
  • :adapter, and Storehouse.Variants's configuration.

warm_later(file, presets, opts \\ [])

@spec warm_later(Storehouse.File.t(), atom() | [atom()], keyword()) ::
  :ok | {:error, term()}

warm/3 out of the request, as a job of the app's runner, when warming is on (enabled?/1), and nothing when it is off. The original is not read: the file's width is all it needs. A file's job waiting is not queued again.

On a queue of its own, so that the app's other work never waits behind it, with Oban:

Storehouse.Responsive.warm_later(file, [:photo],
  up_to: 3840,
  runner: {Storehouse.Runner.Oban, queue: :warm}
)

Options

  • :runner - the runner, in place of the app's (Storehouse.Runner).
  • :enabled - in place of the configured :warm.
  • warm/3's.

widths(file, preset, opts \\ [])

@spec widths(Storehouse.File.t(), atom(), keyword()) :: [
  {pos_integer(), pos_integer()}
]

The widths of file's srcset with preset, each with the step of the ladder it is asked for at: [{width, step}], narrowest first.

# A photograph 1500 wide, a ladder of 640, 1280 and 1920:
Storehouse.Responsive.widths(file, :photo)
#=> [{640, 640}, {1280, 1280}, {1500, 1920}]

For a file whose width is not known, the ladder's.