Webp and picture tags in a rails app

<picture>  <source srcset=”path_to_image.webp” >  <img src=”path_to_image.png’” alt=”alt text” ></picture>
  • Should I find a gem for that (and believe me, there *is* a gem for that)
  • Or, should I go the DIY route
  • Tool size, if you want to kill a fly, you would not use a bazooka. In the same vein, if you want a tool to auto-indent your code, you would not use rubocop
  • Control, if you code it yourself, you can do it precisely tailored to your needs. I love active admin (URL active admin), but if you want to do things differently from what active admin offers, you are in for a complicated process to make them coexist.
  • Dependencies, this one you can kind of control with the gems (or at least keep an eye out), but gems usually call other gems, and that can escalate quickly. If you code it yourself, the size of your dependencies will be smaller, which is good for performance and stability.
  • You can serve a different image depending on the screen size
  • You can serve different file types, and the browser will take the first one it can use
<picture>  <source srcset=”path_to_version_one.webp”>  <source srcset=”path_to_image.webp” >  <img src=”path_to_image.png’” alt=”alt text” ></picture>
  • Inserting the picture tag
  • creating the image tag
  • creating the source tag(s) above it.
let(:picture_object) do  double(    'Photo',     versions: {       thumb: thumb,       thumb_webp: thumb_webp,       medium: medium,       medium_webp: medium_webp,       other: other     },     url: 'https://original_version.jpeg',     exists?: true  )endlet(:webp_file) { double('webp_file', extension: 'webp') }let(:jpeg_file) { double('jpeg_file', extension: 'jpeg') }let(:thumb) { double('thumb', url: 'https://thumb_url.jpg', file: jpeg_file) }let(:thumb_webp) { double('thumb_webp', url: 'https://thumb_url.webp', file: webp_file) }let(:medium_webp) { double('medium_webp', url: 'https://medium_url.webp', file: webp_file) }let(:medium) { double('medium', url: 'https://medium_url.jpg', file: jpeg_file) }let(:other) { double('other', url: 'https://other_url.jpg', file: jpeg_file) }
it ‘responds with a picture tag’ do  expect(helper.picture_tag(picture_object)).to include(‘<picture>’)  expect(helper.picture_tag(picture_object)).to include(‘</picture>’)end
def picture_tag(picture)  tag.pictureend
picture of passing tests in a terminal
# frozen_string_literal: truerequire 'rails_helper'RSpec.describe Pictures::PictureTagHelper, type: :helper do describe '#picture_tag' do  let(:picture_object) do   double(    'Photo',    versions: {     thumb: thumb,     thumb_webp: thumb_webp,     medium: medium,     medium_webp: medium_webp,     other: other    },    url: 'https://original_version.jpeg',    exists?: true   )  end  let(:webp_file) { double('webp_file', extension: 'webp') }  let(:jpeg_file) { double('jpeg_file', extension: 'jpeg') }  let(:thumb) { double('thumb', url: 'https://thumb_url.jpg', file: jpeg_file) }  let(:thumb_webp) { double('thumb_webp', url: 'https://thumb_url.webp', file: webp_file) }  let(:medium_webp) { double('medium_webp', url: 'https://medium_url.webp', file: webp_file) }  let(:medium) { double('medium', url: 'https://medium_url.jpg', file: jpeg_file) }  let(:other) { double('other', url: 'https://other_url.jpg', file: jpeg_file) }  it 'responds with a picture tag' do   expect(helper.picture_tag(picture_object)).to include('<picture>')   expect(helper.picture_tag(picture_object)).to include('</picture>')  end  context 'without versions' do   it 'should have an img tag' do    expect(     helper.picture_tag(      picture_object, image: { alt: 'alt message', width: 300,height: 300 }     )    ).to include('<img alt="alt message" width="300" height="300" src="https://original_version.jpeg" />')   end   it 'should have a source tag and file_type per version ' do    expect(helper.picture_tag(picture_object)).to include(     '<source srcset="https://thumb_url.jpg" type="image/jpeg">'    )    expect(helper.picture_tag(picture_object)).to include(     '<source srcset="https://thumb_url.webp" type="image/webp">'    )    expect(helper.picture_tag(picture_object)).to include(     '<source srcset="https://medium_url.webp" type="image/webp">'    )    expect(helper.picture_tag(picture_object)).to include(     '<source srcset="https://medium_url.jpg" type="image/jpeg">'    )    expect(helper.picture_tag(picture_object)).to include(     '<source srcset="https://other_url.jpg" type="image/jpeg">'    )   end  end  context 'with options' do   context 'with versions option' do    context 'without extensions not flagged' do     it 'accepts symbols' do      expect(       helper.picture_tag(        picture_object, versions: %i[thumb thumb_webp]       )      ).to include('<source srcset="https://thumb_url.jpg" type="image/jpeg">')     end     it 'should have the selected_version with all extensions' do      expect(       helper.picture_tag(        picture_object, versions: [:thumb]       )      ).to include(       '<source srcset="https://thumb_url.webp" type="image/webp">'\       '<source srcset="https://thumb_url.jpg" type="image/jpeg">'      )     end     it 'in the correct order' do      expect(       helper.picture_tag(        picture_object, versions: %i[medium thumb]       )      ).to include(       '<source srcset="https://medium_url.webp" type="image/webp">'\       '<source srcset="https://medium_url.jpg" type="image/jpeg">'\       '<source srcset="https://thumb_url.webp" type="image/webp">'\       '<source srcset="https://thumb_url.jpg" type="image/jpeg">'      )     end     it 'should not have the versions not included' do      expect(       helper.picture_tag(        picture_object, versions: [:thumb]       )      ).not_to include('<source srcset="https://medium_url.jpg" type="image/jpeg">')      expect(       helper.picture_tag(        picture_object, versions: %i[thumb medium]       )      ).not_to include('<source srcset="https://other_url.webp" type="image/jpeg">')     end    end    context 'without extensions flagged' do     it 'accepts symbols' do      expect(       helper.picture_tag(        picture_object, without_extensions: true,        versions: %i[thumb thumb_webp]       )      ).to include('<source srcset="https://thumb_url.jpg" type="image/jpeg">')     end     it 'should have the selected_version' do      expect(       helper.picture_tag(        picture_object, without_extensions: true,        versions: [:thumb]       )      ).to include('<source srcset="https://thumb_url.jpg" type="image/jpeg">')     end     it 'in the correct order' do      expect(       helper.picture_tag(        picture_object, without_extensions: true,        versions: %i[medium thumb]       )      ).to include(       '<source srcset="https://medium_url.jpg" type="image/jpeg">'\       '<source srcset="https://thumb_url.jpg" type="image/jpeg">'      )     end     it 'should not have the versions not included' do      expect(       helper.picture_tag(        picture_object, without_extensions: true,        versions: [:thumb]       )      ).not_to include('<source srcset="https://medium_url.jpg" type="image/jpeg">')      expect(       helper.picture_tag(        picture_object, without_extensions: true,        versions: [:thumb]
)
).not_to include('<source srcset="https://thumb_url.webp" type="image/webp">') expect( helper.picture_tag( picture_object, without_extensions: true, versions: %i[thumb medium] ) ).not_to include('<source srcset="https://other_url.webp" type="image/jpeg">') end end end end endend
# frozen_string_literal: truemodule Pictures # These helpers help you build versions of # a picture in order to user the 'picture' tag module PictureTagHelper  # Possible options:  #  - versions (should be a version existing  # in the carrierwave uploader) [Array] of symbols or strings, order is important!  # - without_extensions [Boolean] if true does not add  # webp version (webp version must exist on the uploader),  # other extensions can be easily added  def picture_tag(picture, **options)   image_options = options[:image] || {}   return image_tag(picture.default_url, image_options) unless picture.exists?   picture_options = options[:picture] || {}   tag.picture(picture_options) do    "#{build_source_tags(picture, options).join}"\    " #{image_tag(picture.url, image_options)}".html_safe   end  end  # Possible options:  #  - versions (should be a version existing  # in the carrierwave uploader) [Array] of symbols or strings, order is important!  # - without_extensions [Boolean] if true does not add  # webp version (webp version must exist on the uploader),  # other extensions can be easily added  def source_tags(picture, **options)   build_source_tags(picture, options).join.to_s.html_safe  end  private  def build_source_tags(picture, options)   versions(picture, options).map do |_version_name, version_object|    source_tag(version_object)   end  end  def versions(picture, options)   return picture.versions.slice(*build_extensions(options)) if options[:versions]   picture.versions  end  def source_tag(version)   return '' unless version.file   tag.source srcset: version.url.gsub(/\(|\)/, '_'), type: image_type(version.file.extension)  end  def image_type(extension)   Mime::Type.register 'image/webp', :webp   Mime::Type.lookup_by_extension(extension).to_s  end  def build_extensions(options)   return options[:versions] if options[:without_extensions]   options[:versions].flat_map do |version|    ordered_extentions.map { |extension| "#{version}_#{extension}".to_sym } << version.to_sym   end  end  def ordered_extentions   ['webp']  end endend
  1. Have a breakpoints Array in the options Hash
  2. Change the versions key of the options to be a Hash with the version name as the key and the break-point as the value

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Pablo Curell Mompo

Pablo Curell Mompo

Full Stack developer @ Seraphin, learned how to code @ Le Wagon. I love coding and plan to keep learning as much as I can about it :)