Skip to content

Responsive image helpers #19

@dbernheisel

Description

@dbernheisel

I have an interest in implementing responsive image helpers for a project I'm on. I'd be happy to contribute them to Brady instead of making my own library. Here are my thoughts and couple steps to get there.

I'll be happy to contribute these functions in separate PRs, as they'd be helpful by themselves for others.

  • Introduce a data_uri helper to inline images.
  • Introduce a srcset attribute helper.
  • Introduce a picture_tag helper.

The idea is that if my /assets/images folder contains original images, and my asset pipeline has a task that will produce responsive images from those original images and place them in /priv/static/images with some sort of naming convention, then I can refer to them in Phoenix templates. Right now, it's really tedious to use responsive images in Phoenix, as I have to write content tags for each format and version for each image on the page.

Here are some function docs to help illustrate how they could work:

  @doc """
  Encodes an image to base64-encoded data uri, compatible for img src attributes. Only recommended
  for files less than 2kb

  Ex:
      Brady.data_uri("placeholder.gif")
      # => "data:image/gif;base64,iVBORw0KGgoAAAA"


  Ex:
    Brady.data_uri("super-large-file.bmp")
    # => Warning: The file "super-large-file.bmp" is large and not recommended for inlining in templates. Please reconsider inlining this image.
    # => "data:image/bmp;base64,lkjfdsjlkjkldjlsdfkjlkdfskldsj
  """
  @doc """
  Generate a string representing a srcset of assets based on the configured list of optimized
  images. For example, if your asset pipeline generates a 2x, 3x, and 1x version of all images, then
  you can configure "1x, 2x, 3x" globally, and then have Brady generate a list of images suitable
  for image srcsets.

  Ex:
      config :brady,
        image_sets: ["_default", "-2x", "@3x"], # always put your default first for 1x
        static_path_generator: {MyAppWeb.Router.Helpers, :static_path, 2}

      Brady.srcset(@conn, "images/image.png")
      # => "
        images/image_default.png 1x,
        images/image-2x.png 2x
        images/image@3x.png 3x
        "

      Used with Phoenix.HTML:
      img_tag(
        Routes.static_path(@conn, "images/image.jpg"),
        srcset: Brady.srcset(@conn, "images/image.jpg"),
        alt: "My alt text"
      )

      # =>
      # <img srcset="images/image_default.jpg 1x, images/image-2x.jpg 2x, images/image@3x.jpg 3x"
      #      src="images/image.jpg"
      #      alt="My alt text" />
  """
  @doc ~S"""
  Adds a <picture> tag with a <source> tag for each source. The placeholder would be the first asset
  shown, and is the <img> tag and also serves as a fallback for browsers without good support. It's
  recommended to have the placeholder be inlined and small. The type is inferred from the extension.

  Brady.picture_tag(
    placeholder: Brady.data_uri("placeholder.png"),
    alt: "My alt text",
    sources: [
      Routes.static_path(@conn, "my/image.webp"),
      Routes.static_path(@conn, "my/image.png")
    ]
  )
  # =>
  # <picture>
  #   <source type="image/webp" srcset="my/image.webp" />
  #   <source type="image/png" srcset="my/image.png" />
  #   <img src="data:image/png;base64,iVBORw0KGgoAAAA" alt="My alt text" />
  # </picture>
  #

  Brady.picture_tag(
    placeholder: Brady.data_uri("placeholder.png"),
    alt: "My alt text",
    sources: [
      [srcset: Brady.srcset(@conn, "my/image.png")],
      [media: "(max-width: 1024px)", srcset: Routes.static_path(@conn, "my/image.jpg")],
      [media: "(max-width: 768px)", srcset: Brady.srcset(@conn, "my/image-smaller.jpg")]
    ]
  )

  # =>
  # <picture>
  #   <source type="image/png"
  #           srcset="my/image.png 1x, my/image@2x.png 2x" />
  #   <source type="image/jpg"
  #           media="(max-width: 1024px)"
  #           srcset="my/image.jpg" />
  #   <source type="image/webp"
  #           media="(max-width: 768px)"
  #           srcset="my/image-smaller.jpg 1x, my/image-smaller@2x.jpg 2x" />
  #   <img src="data:image/png;base64,iVBORw0KGgoAAAA" alt="My alt text" />
  # </picture>


  config :brady,
    image_sets: [
      "-default", # (assumes there is image-default.ext)
      "@2x", # (assumes there is image@2x.ext)
      "-3x" # (assumes there is image-3x.ext)
    ], # always put your 1x first
    image_formats: ["webp", "jpg"],
    static_path_generator: {MyAppWeb.Router.Helpers, :static_path, 2},
    default_picture_breakpoints: [
      {"(max-width: 300px)", "-smaller"}, # (assumes there is image-smaller.ext)
      {"(max-width: 768px)", "-default"}, # (assumes there is image-default.ext)
      {"(max-width: 1400px)", "-large"}, # (assumes there is image-larger.ext)
    ]

  Brady.picture_tag(
    @conn,
    placeholder: Brady.data_uri("placeholder.png"),
    alt: "My alt text",
    source: "my/image.jpg" # <= relies on defaults set in config
  )

  # =>
  # <picture>
  #   <source type="image/webp"
  #           media="(max-width: 300px)"
  #           srcset="my/image-smaller.webp 1x, my/image-smaller@2x.webp 2x, my/image-smaller-3x.webp 3x" />
  #   <source type="image/webp"
  #           media="(max-width: 768px)"
  #           srcset="my/image-default.webp 1x, my/image-default@2x.webp 2x, my/image-default-3x.webp 3x" />
  #   <source type="image/webp"
  #           media="(max-width: 1400px)"
  #           srcset="my/image-larger.webp 1x, my/image-larger@2x.webp 2x, my/image-larger-3x.webp 3x" />
  #   <source type="image/webp"
  #           srcset="my/image-default.webp 1x, my/image-default@2x.webp 2x, my/image-default-3x.webp 3x" />
  #   <source type="image/jpg"
  #           media="(max-width: 300px)"
  #           srcset="my/image-smaller.jpg 1x, my/image-smaller@2x.jpg 2x, my/image-smaller-3x.jpg 3x" />
  #   <source type="image/jpg"
  #           media="(max-width: 768px)"
  #           srcset="my/image-default.jpg 1x, my/image-default@2x.jpg 2x, my/image-default-3x.jpg 3x" />
  #   <source type="image/jpg"
  #           media="(max-width: 1400px)"
  #           srcset="my/image-larger.jpg 1x, my/image-larger@2x.jpg 2x, my/image-larger-3x.jpg 3x" />
  #   <source type="image/jpg"
  #           srcset="my/image-default.jpg 1x, my/image-default@2x.jpg 2x, my/image-default-3x.jpg 3x" />
  #   <img src="data:image/png;base64,iVBORw0KGgoAAAA" alt="My alt text" />
  # </picture>
  """

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions