Google-like letter avatars using ERB-generated SVGs

Posted on 2021-06-30 by Karol Bąk Comments
ruby
rails
svg

Introduction

Creating user avatars is a pretty simple thing. Gems like avatarly or letter_avatar can generate PNG images with user initials. This time I would like to show you a different approach - SVG generated by the standard ruby ERB template engine.

Why?

There are some advantages of using SVG instead of PNG in this case:

  • image generation is faster
  • it doesn’t require any additional dependencies like imagemagick
  • SVGs scale better on high resolution / high pixel ratio devices

SVG images in <img> are widely supported in the browsers - https://caniuse.com/svg-img. Although it has very limited support if it comes to email clients - https://www.caniemail.com/features/image-svg/. So if you need to place avatars inside emails I wouldn’t recommend this solution.

Implementation

Let’s start with our controller. We want our URL to look like this: /users/:user_id/avatar.svg. If you already have a users controller you can add it as a method there or add a nested resource:

# config/routes.rb
resources :users do
  resource :avatar, only: :show
end

In my example, I will use user initials generated by taking first letters from two first words from the user’s full name.

# app/controllers/avatars_controller.rb

class AvatarsController < ::ApplicationController
  before_action :set_user

  def show
    @letters = @user.full_name.split.map(&:first).join.upcase[0..1]
  end

  private

  def set_user
    @user = User.find(params[:user_id])
  end
end

Now we can create a view for our action with .svg.erb extension. Let’s start with a simple image with red background and white letters. app/views/avatars/show.svg.erb:

<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
  <rect width="100%" height="100%" fill="#c5523f"/>
  <text fill="#fff" font-family="Helvetica,Arial,sans-serif" font-size="26" font-weight="500" x="50%" y="55%" dominant-baseline="middle" text-anchor="middle">
    <%= @letters %>
  </text>
</svg>

Result:

Avatar 1

Our text is centered thanks to these attributes: x="50%" y="55%" dominant-baseline="middle" text-anchor="middle"

That was pretty simple, right? Now let’s add more colors. We can pick color for each user using id.

# app/controllers/avatars_controller.rb

class AvatarsController < ::ApplicationController
  COLORS = %w(#f2b736 #c5523f #499255 #1875e5).freeze

  before_action :set_user

  def show
    @letters = @user.full_name.split.map(&:first).join.upcase[0..1]
    @background = COLORS[@user.id % COLORS.length]
  end

  private

  def set_user
    @user = User.find(params[:user_id])
  end
end

Our updated view:

<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
  <rect width="100%" height="100%" fill="<%= @background %>"/>
  <text fill="#fff" font-family="Helvetica,Arial,sans-serif" font-size="26" font-weight="500" x="50%" y="55%" dominant-baseline="middle" text-anchor="middle">
    <%= @letters %>
  </text>
</svg>

Results:

Avatar 2 Avatar 4

Alright, now we have simple user avatars ready to use! But what if we would like to use some non-standard font? Unfortunately, things get complicated here. In theory, we can add a <style> block inside the SVG file and load a font from Google Fonts or any other place. It will work if you open the file directly in the browser but it won’t if you link to it via <img> tag (browser will ignore external import). Don’t worry, there is a way to make it work - we just need to place base64-encoded font inside SVG.

In our controller we will add additional const with an encoded font (since we don’t want to do it on each request, just once during initialization):

# app/controllers/avatars_controller.rb

class AvatarsController < ::ApplicationController
  COLORS = %w(#f2b736 #c5523f #499255 #1875e5).freeze
  ENCODED_FONT = Base64.encode64(Rails.root.join('app/assets/fonts/OpenSans-SemiBold.ttf').read)
                       .delete("\n").freeze

  before_action :set_user

  def show
    @letters = @user.full_name.split.map(&:first).join.upcase[0..1]
    @background = COLORS[@user.id % COLORS.length]
  end

  private

  def set_user
    @user = User.find(params[:user_id])
  end
end

And update our view:

<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
  <style>
    @font-face {
      font-family: "OpenSans";
      font-weight: 500;
      src: url(data:application/x-font-ttf;base64,<%= ::AvatarsController::ENCODED_FONT %>);
    }
  </style>
  <rect width="100%" height="100%" fill="<%= @background %>"/>
  <text fill="#fff" font-family="OpenSans,Arial,sans-serif" font-size="26" font-weight="500" x="50%" y="55%" dominant-baseline="middle" text-anchor="middle">
    <%= @letters %>
  </text>
</svg>

And it’s done! Our final result:

Avatar 3

Conclusion

Creating SVG using ERB is really easy since it’s basically an XML file. With the solution above we are able to create simple user avatars without any additional libraries. If you have any questions, don’t hesitate to ask in the comments below.