Skip to content

Instantly share code, notes, and snippets.

@raybrownco
Last active July 10, 2021 13:37
Show Gist options
  • Select an option

  • Save raybrownco/0047ef8d7eaec0bf31bb to your computer and use it in GitHub Desktop.

Select an option

Save raybrownco/0047ef8d7eaec0bf31bb to your computer and use it in GitHub Desktop.
Inline SVG in Middleman
# Middleman - Inline SVG Helper
# ------------------------------------------------------------------------------
# 1. Save this file at `[project_root]/helpers/image_helpers.rb`
# 2. Restart your local Middleman server if it's running
# 3. Embed SVG files into your template files like so:
#
# <%= inline_svg("name/of/file.svg") %>
#
# The helper also allows for CSS classes to be added:
#
# <%= inline_svg("name/of/file.svg", class: "my-addl-class") %>
#
module ImageHelpers
# Embed SVG images inline within a Middleman template.
# ---
# Adapted from the work of James Martin and Steven Harley.
# Reference: https://robots.thoughtbot.com/organized-workflow-for-svg
def inline_svg(filename, options = {})
asset = sprockets.find_asset(filename)
# If the file wasn't found, embed error SVG
if asset.nil?
%(
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 30"
width="400px" height="30px"
>
<text font-size="16" x="8" y="20" fill="#cc0000">
Error: '#{filename}' could not be found.
</text>
<rect
x="1" y="1" width="398" height="28" fill="none"
stroke-width="1" stroke="#cc0000"
/>
</svg>
)
# If the file was found, parse it, add optional classes, and then embed it
else
file = asset.source.force_encoding("UTF-8")
doc = Nokogiri::HTML::DocumentFragment.parse file
svg = doc.at_css "svg"
if options[:class].present?
svg["class"] = options[:class]
end
doc
end
end
end
@AntonKL
Copy link

AntonKL commented Oct 22, 2015

I've placed my assets in a assets folder. I'm then including the file by <%= inline_svg("/assets/images/my-image.svg") %>.

I get a not found triggered from the helper. Is this setup invalid?

My folder structure

helpers
- image_helpers.rb
source
- index.html.erb
  assets
    images
    - my-image.svg

@AntonKL
Copy link

AntonKL commented Oct 22, 2015

Oh never mind. It's me being dumb. Didn't realized you used sprockets to locate it by filename and not url.

@hansondr
Copy link

hansondr commented Jun 7, 2016

modified this as follows:

# lib/image_helpers.rb
module ImageHelpers
  # usage <%= inline_svg("logo"); %> assuming logo.svg is stored at source/images/svg/logo.svg
  def inline_svg(filename, options = {})
    asset = "source/images/svg/#{filename}.svg"

    if File.exists?(asset)
      file = File.open(asset, 'r') { |f| f.read }
      doc = Nokogiri::HTML::DocumentFragment.parse(file)
      svg = doc.at_css("svg")

      if options[:class].present?
        svg["class"] = options[:class]
      end

      doc
    else
      %(
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 30"
          width="400px" height="30px"
        >
          <text font-size="16" x="8" y="20" fill="#cc0000">
            Error: '#{filename}' could not be found.
          </text>
          <rect
            x="1" y="1" width="398" height="28" fill="none"
            stroke-width="1" stroke="#cc0000"
          />
        </svg>
      )
    end
  end
end

Then add the following to your config.rb

...

require "lib/image_helpers"
helpers ImageHelpers

@cveneziani
Copy link

cveneziani commented Jan 5, 2017

For the ones that don't like to add the huge Nokogiri as dependency, I propose an alternative with Oga.

By just replacing Nokogiri implementation:

doc = Nokogiri::HTML::DocumentFragment.parse(file)
svg = doc.at_css("svg")

if options[:class].present?
  svg["class"] = options[:class]
end

doc

With Oga implementation:

css_class = options[:class]
return file if css_class.nil?

document        = Oga.parse_xml(file)
svg             = document.css('svg').first
class_attribute = svg.attribute(:class)

if class_attribute
  class_attribute.value = css_class
else
  class_attribute = Oga::XML::Attribute.new(name: :class, value: css_class)
  svg.add_attribute(class_attribute)
end

document.to_xml

BTW I also avoid parsing the XML if no class option is provided. I directly return the content of the file =)

@cveneziani
Copy link

Refactored "a bit" :)

css_class = options[:class]
return file if css_class.nil?

document = Oga.parse_xml(file)
svg      = document.css('svg').first

svg.set(:class, css_class)

document.to_xml

@hellokatili
Copy link

Is there a way to modify this helper so I can use it with HAML-SVGs? (I have to set the xlink:href of an image with the image_path helper.)

Example SVG:

%svg{viewbox: '0 0 200 200', xmlns: 'http//www.w3.org/2000/svg', 'xmlns:xlink' => 'http//www.w3.org/1999/xlink'}
  %image#image{height: '20', width: '20', 'xlink:href' => image_path('image.jpg')}

@allanwhite
Copy link

I found this gist and approach really very helpful, and am using it now in our middleman-driven site. I made a few tweaks someone might find useful:

# /helpers/image_helpers.rb 
module ImageHelpers
  # usage <%= inline_svg("logo"); %> assuming logo.svg is stored at source/assets/icons/logo.svg
  def inline_svg(filename, options = {})
    asset = "source/assets/icons/#{filename}.svg"

    if File.exists?(asset)
      file = File.open(asset, 'r') { |f| f.read }
      # we pass svg-targeting css classes through here. The class targets fill, stroke, poly, circle, etc. 
      css_class = options[:class]
      aspect_ratio = options[:preserveAspectRatio]
      # added some aspect-ratio settings; default covers most of our svg use cases. We want all our SVG icon artwork to scale to fit. This could be an argument if needed.
      radio_default = "xMidYMid meet"
      return file if css_class.nil?
      document = Oga.parse_xml(file)
      svg      = document.css('svg').first
      svg.set(:class, css_class)
      svg.set(:preserveAspectRatio, radio_default)
      document.to_xml

    else
      # added a little more helpful info here so it's easier to track down a problem.
      puts "inline_svg '#{asset}' at #{current_page.url} could not be found!"
      %(
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 60" width="400px" height="60px">
          <text font-size="12" x="8" y="20" fill="#cc0000">
            Error: '#{asset}' could not be found.
          </text>
          <rect x="1" y="1" width="398" height="38" fill="none" stroke-width="1" stroke="#cc0000" />
        </svg>
      )
    end
  end
end

@raybrownco
Copy link
Author

I've updated this Gist to perform better and support more attributes. This work relies on the improvements made by @cveneziani and @allanwhite. Thanks so much for your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment