brady
brady copied to clipboard
Responsive image helpers
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_urihelper to inline images. - Introduce a
srcsetattribute helper. - Introduce a
picture_taghelper.
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>
"""
@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...
@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 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?
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).
🆒 Then yes, all this seems perfectly reasonable and I'd keep the names you have.