fteschke

fteschke

How do you create responsive images in your Phoenix apps?

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

Most Liked

Last Post!

fteschke

fteschke

This might be a useful reference

Author avatar looks familiar :slight_smile:

Where Next?

Popular in Discussions Top

AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31695 143
New
AstonJ
I’ve just started the Phoenix part of the utterly brilliant online course by @pragdave. On generating the Phoenix app he uses the --no-ec...
New
PragTob
Hey everyone, this has been brewing in my head some time and it came up again while reading Adopting Elixir. GenServers, supervisors et...
New
lorenzo
Hey everone! I created a prototype for my app using Nodejs for the api. But the framework I chose wasnt great (in general theresnt any g...
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
tomekowal
Hey guys! I want to create a toy project that shows a chart of temperature over time and updates every 5 seconds. I feel LiveView is per...
New
marciol
Please, let me know if this kind of discussion already took place in another topic . Hi all, how do you consider if is better to build ...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54006 488
New
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New

We're in Beta

About us Mission Statement