How do you create responsive images with phoenix?
I’d love to merge my separate 11ty/netlify landing page into the Phoenix app.
Responsive images are the last big stumbling block for me. Most JAMStack frameworks have good out-of-the-box solutions, e.g. next.js and gatsby.
It would be great to have an easy to use, performant solution for Phoenix.
That doesn’t impact page loading speeds/page rank.
Here’s my best solution so far. Resized using Image
at compile time.
Stored in priv/static/resized
, served and cached via normal Plug.Static setup.
Would love to get some feedback and to hear about how you guys handle this.
Usage:
use ResponsiveImage
~H(<img src={src("input.jpg", 300)} />)
~H(<img srcset={srcset("input.jpg", [300, 600, 900])} src={...} sizes="50vw" />)
Module:
defmodule ResponsiveImage do
defmacro __using__(_opts) do
quote do
import unquote(__MODULE__)
Module.register_attribute(__MODULE__, :image, accumulate: true)
@before_compile unquote(__MODULE__)
end
end
defmacro src(path, width) do
Module.put_attribute(__CALLER__.module, :image, {path, width})
quote do
unquote(img_src(path, width))
end
end
defmacro srcset(path, widths) do
for width <- widths, do: Module.put_attribute(__CALLER__.module, :image, {path, width})
quote do
unquote(widths |> Enum.map(&"#{img_src(path, &1)} #{&1}w") |> Enum.join(", "))
end
end
defmacro __before_compile__(_env) do
{duration, _} =
:timer.tc(fn ->
ResponsiveImage.resize(Module.get_attribute(__CALLER__.module, :image))
end)
IO.puts("Image resize took #{duration / 1_000_000}s")
end
def resize(attr) when is_list(attr) do
for {path, width} <- attr, do: resize(path, width)
end
def resize(path, width) do
out_path = out_path(path, width)
if not File.exists?(out_path) do
IO.puts("Writing #{out_path}")
out_path |> Path.dirname() |> File.mkdir_p!()
path
|> in_path()
|> Image.open!()
|> then(&Image.resize!(&1, width / Image.width(&1)))
|> Image.write!(out_path)
end
end
defp without_ext(path), do: "#{Path.dirname(path)}/#{Path.basename(path, Path.extname(path))}"
defp in_path(path), do: "priv/static/images/#{path}"
defp img_src(path, width), do: "/resized/#{without_ext(path)}_#{width}.avif"
defp out_path(path, width), do: "priv/static#{img_src(path, width)}"
end