Scenario
Say you have an image that you want to change appearance as the user interacts with it. In my case, I have a ‘back to top’ button like this:
When the person hovers over it to click, it should look different to let them know it’s interactive, something like this:
First solution: Swappable background images
The first way I tried to get this look was to upload the two images, set one as the background image normally and have the other switch out on hover, like this:
html
/* shared/_header.html.erb */
<a href="#top" class="top-button"></a>
css
/* stylesheets/header.scss */
.top-button {
content: "";
display: inline-block;
width: 50px;
height: 50px;
background-image: url('top-button.svg');
background-position: center;
background-repeat: no-repeat;
background-size: 50px 50px;
}
.top-button:hover,
.top-button:focus {
background-image: url('top-button-hover.svg');
}
Now this works fine, the two images switch out and the user can clearly see it is a clickable object. Our transition now looks like this:
Although this was a great improvement, there are a couple of issues in this solution that bother me: 1. The transition is a bit sudden, it would be nice to have it smoother 1. There are now two seperate images that need to load - This increases load time - There may be a flash between the two states as it loads the second image on hover rather than at page load
Smoother transitions
With CSS3, animations transitions are easier than ever and usually only require a line or two of code to make it look smoother.
The code below has a transition on the background-image, as that’s the property we’re changing, ease
is a type of transition and 0.3s is the duration, or how fast the animation is.
```css
/* stylesheets/header.scss */
.top-button {
transition: background-image ease 0.3s; } ```
OK, so it’s looking a lot nicer now, time to do some cross browser testing. This uncovers two major issues that need to be addressed:
- We’re still getting the flash of no image as it changes state for the first time.
- It’s doing something strange in Safari: on hover the image looks like it’s getting bigger and going down to the bottom right corner like this:
If the image is in SVG format we can solve both these problems by embedding it into the HTML. Otherwise, turning transitions off for Safari can also work for a quick fix (ref: browserhacks).
Note: There are other methods documented for the safari bug; I tried quite a few but this was the only thing that worked for me.
css
/* stylesheets/header.scss */
_::-webkit-full-page-media,
_:future,
:root .top-button {
transition: none !important;
}
## Second solution: Embedding the SVG using Ruby
This is the solution I found in the end which fixed all the issues I was having. By embedding the SVG file of the image, you only need one version as you can adjust your hover CSS to update the styling of the SVG directly. This means only one image is needed, so no extra load time, no flash and no Safari bug. It also means you have greater control of your image and you can access its properties directly with inspect element in the browser.
The code I used for this has been adapted from the guide A more efficient and organised workflow for SVGs.
erb
# app/helpers/application_helper.rb
def embedded_svg(filename, options = {})
file = File.read(Rails.root.join "app", "assets", "images", filename)
doc = Nokogiri::HTML::DocumentFragment.parse file
svg = doc.at_css "svg"
if options[:class].present?
svg["class"] = options[:class]
end
raw doc
end
You can then embed the SVG in your view, just like you would using an image_tag
.
erb
/* shared/_header.html.erb */
<a href="#top" class="top-button">
<%= embedded_svg "top-button.svg", class: "up_arrow" %>
</a>
The styling below is a guide only; you will have to inspect your SVG to get the specific id’s and elements.
Note: That instead of background-color
and border-color
you should try fill
and stroke
. All of which can be transitioned in the same way as before.
css
/* stylesheets/header.scss */
.top-button .up_arrow svg #circle {
transition: fill 0.3s ease;
}
.top-button:hover .up_arrow svg #circle,
.top-button:focus .up_arrow svg #circle {
fill: #00B293;
circle {
stroke: white;
}
#arrow {
fill: white;
}
}
## Where to next?
SVG’s are very powerful when combined with CSS animations. Don’t stop with here with changing the colours, check out some of these resources with how you can style SVG’s in your project: