Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Responsive image helpers #19

Open
dbernheisel opened this issue Oct 22, 2018 · 5 comments
Open

Responsive image helpers #19

dbernheisel opened this issue Oct 22, 2018 · 5 comments
Assignees

Comments

@dbernheisel
Copy link
Collaborator

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/[email protected] 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/[email protected] 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/[email protected] 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/[email protected] 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 [email protected])
      "-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/[email protected] 2x, my/image-smaller-3x.webp 3x" />
  #   <source type="image/webp"
  #           media="(max-width: 768px)"
  #           srcset="my/image-default.webp 1x, my/[email protected] 2x, my/image-default-3x.webp 3x" />
  #   <source type="image/webp"
  #           media="(max-width: 1400px)"
  #           srcset="my/image-larger.webp 1x, my/[email protected] 2x, my/image-larger-3x.webp 3x" />
  #   <source type="image/webp"
  #           srcset="my/image-default.webp 1x, my/[email protected] 2x, my/image-default-3x.webp 3x" />
  #   <source type="image/jpg"
  #           media="(max-width: 300px)"
  #           srcset="my/image-smaller.jpg 1x, my/[email protected] 2x, my/image-smaller-3x.jpg 3x" />
  #   <source type="image/jpg"
  #           media="(max-width: 768px)"
  #           srcset="my/image-default.jpg 1x, my/[email protected] 2x, my/image-default-3x.jpg 3x" />
  #   <source type="image/jpg"
  #           media="(max-width: 1400px)"
  #           srcset="my/image-larger.jpg 1x, my/[email protected] 2x, my/image-larger-3x.jpg 3x" />
  #   <source type="image/jpg"
  #           srcset="my/image-default.jpg 1x, my/[email protected] 2x, my/image-default-3x.jpg 3x" />
  #   <img src="data:image/png;base64,iVBORw0KGgoAAAA" alt="My alt text" />
  # </picture>
  """
@drapergeek
Copy link
Collaborator

@dbernheisel I think these are all great ideas and IMHO, I think they'd make great additions to Brady. I'd much rather use Brady for all my basic frontend needs than to pull in a multitude of options.

For the srcset one, what do you think about naming it something a bit more descriptive like responsive_image? This one would be great, I know that we used that a lot on previous projects.

I think something more descriptive for the picture_ tag would also be nice but nothing is coming to mind...

@dbernheisel
Copy link
Collaborator Author

@drapergeek responsive_image to me implies it's generating an <img> tag, but it's not. It's only providing a string to be used within a srcset="x" attribute inside of the image tag.

More descriptive name or docs for picture_tag?

@drapergeek
Copy link
Collaborator

@drapergeek responsive_image to me implies it's generating an  tag, but it's not. It's only providing a string to be used within a srcset="x" attribute inside of the image tag.

This is what I get for skimming...you're totally correct.

Forgive my ignorance, I haven't been doing frontend work recently. Is the picture/source option now the preferred method for responsive images or should one still be using img with the srcset option?

@dbernheisel
Copy link
Collaborator Author

dbernheisel commented Nov 27, 2018

Here's a good small explanation of the difference: https://dev.to/jessefulton/explain-htmls-img-srcset-vs-picture-tag-like-im-five-167p

Both can accomplish "responsive images", but have some different uses. tldr:

  • picture tag can provide different images on media queries; eg, if the user is in "portrait mode", a landscape image can be cropped into portrait mode. If the user is on a desktop with the browser really wide, they can get the original landscape wide image.
  • An image tag with a srcset can't do that-- it can only switch resolution/dpi of what should be the same image (just different dpi).

@drapergeek
Copy link
Collaborator

🆒
Then yes, all this seems perfectly reasonable and I'd keep the names you have.

@dbernheisel dbernheisel self-assigned this Dec 30, 2018
dbernheisel added a commit that referenced this issue Dec 30, 2018
Effort outlined in #19

Introduce a `Brady.data_uri(image_path)` function that will base64 encode the image and output a data uri compatible with `<img src="x">`.

The function will emit warnings when it is inlining images larger than 2048 bytes (after base64 encoding) by default, but can be configured in Mix to have a different threshold.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants