The following is a guest post by Dylan Winn-Brown, who shows us a performant way to accomplish this design effect.
Whilst working on a client’s website recently, I was asked to replicate an effect like this.
This type of effect is notably used in portfolio-type situations where the design intends to show both visual and informational details.
There are many different possible methods
As I had never created an effect like this before, I began to take a look at different ways of doing this and came across a number of different methods.
One option was to use a jQuery Plugin. This one wasn’t quite the effect I was after, and certainly not very lightweight.
Another option was to position an <img>
within the container and manipulate it with CSS. There could be some potential benefits here, like being able to set the source with srcset
so that the image used is performance and device-appropriate.
In my situation, I wanted to manage the effect entirely in CSS, so I went for that.
Basic functionality
In order to achieve optimal performance, I decided to use the CSS transform
property to handle the enlargement of the image. (CSS animations benefit from hardware acceleration and as a result appear smoother than other methods of animating.)
Rather than an <img>
, I used an additional <div>
inside the parent to act as the image. The structure being:
<div class="parent">
<div class="child"></div>
</div>
First we specify the dimensions for the parent element. Then the child can fill the parent using width: 100%
and height: 100%;
, as well as set the background image, ensuring it scales to cover the area.
.parent {
width: 400px;
height: 300px;
}
.child {
width: 100%;
height: 100%;
background-color: black; /* fallback color */
background-image: url("images/city.jpg");
background-position: center;
background-size: cover;
}
We then add hover effects to our parent element which will affect our child element. A focus style is good for accessibility as well:
.parent:hover .child,
.parent:focus .child {
transform: scale(1.2);
}
You may want to use a tool for adding prefixes for the best possible browser support.
To finish up the basic effect, we can add some transitions to our child element’s normal state:
transition: all .5s;
If you want to add a color overlay, you can make use of pseudo elements like ::before
:
.child::before {
content: "";
display: none;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
background-color: rgba(52, 73, 94, 0.75);
}
.parent:hover .child:before,
.parent:focus .child:before {
display: block;
}
Now when we hover on the parent element, the child element should show a color overlay!
Finally, we’ll cover how to add some text to show on our overlay. We can add an element to our current child element like so:
<div class="parent">
<div class="child">
<span>Hello</span>
</div>
</div>
We can give our <span>
some style:
span {
color: white; /* Good thing we set a fallback color! */
font-family: sans-serif;
padding: 25%;
position: absolute;
}
and we can make it visible only when we hover on the .parent
:
.parent:hover span,
.parent:focus span {
display: block;
}
Live Demo
See the Pen Image zoom on hover – portfolio websites by Dylan (@dwinnbrown) on CodePen.
Mobile Support
If the containers are links and the hover states don’t reveal any essential information, you might just leave it alone.
If the hover states are important, in order for this to work on touch screens, we can use empty onclick=""
handlers on our .parent
containers. Unfortunately I couldn’t come across another way of doing this but if any of you do, let me know in the comments!
I think iOS dispatches hiver states if the element has
cursor: pointer
; is it the only one?Mobile Support – Worked in Silk Browsernon my Kindle (5th generation/2015)
I know that you are trying to show the effect, but would suggest to make it more subtle. Many so-called coder comes to your site a just copy paste it. And then we would see the eye-tearing effect everywhere. Thank you for consideration.
—
Like http://codepen.io/anon/pen/GqPqpG
Thanks
You didn’t discuss the method that the Fitbit website that was used in the opening paragraph does the effect (and the way I first considered approaching this effect), which is to use the
background-size
property.Using background images and then animating the size on hover feels like a solid approach as it is keeping all of the code contained to the stylesheet, which you mentioned as a requirement, and also doesn’t add any extra elements to the html for achieving styling concerns, as with the method you went with.
The trouble with this is that
background-size
won’t animate nearly as smoothly as a transform will. I also dig how clean and simple that code is, but performance win of just using a transform is worth it.Something about using background images for an image gallery just strikes me as wrong.
I know the point was to try to apply this effect with just some
<div>
tags and CSS, but I went ahead and created a version for anyone looking to do this with<img>
tags instead: http://codepen.io/anon/pen/EyGmQxAnd its a nasty resource hog, too. CPU peaks up to 11 as soon as I hover over the images .. so .. nay. not mine (thats on a simple i5-2520 / T520).
IMHO, the advantage of the background solution is, that resizing is a) much smoother (and less resource-eating) and b) fun with media queries = not needing to involve the current dismal state of “responsive images”.
cu, w0lf.
Also I would like to note another problem with
background-image
– if you are getting image sources from server you can’t easily use them as you can with regular<img>
tag.@fwolf, not sure why you would see a CPU spike when hovering over the images, since CSS transforms would be handled by the GPU.
If there really is some kind of performance gain by using background images, then progressive enhancement through JavaScript should be used to replace the images with the background image version, but I’m not convinced that there is a performance issue.
My primary grievances with the background images are with separation of concerns, semantics, and accessibility. Best practices appear to be playing second fiddle to eye candy more often as the Internet evolves.
There is a problem with your background image demo. The viewport (viewable area of the demo page) scrolls up and down automatically when hovering on and off the images. (The images can’t be shown in full on my [MacBook] monitor.) That doesn’t happen with the CSS-Tricks demo.
Why not combine the ideas and place the background-image in the ::before, do the transform on that and hide the image element in a way screen readers can still access it. Also, the figure and figcaption elements are definitely more preferable than div.parent, div.child and a span.
Small addendum: Another comment I agreed with is that the animation is far to strong. I found values of ~0.9s transform time and a scaling factor between 5 and 10% far more pleasing. This effect should be subtle. But this is subjective of course.
May I ask why there is another
<div>(class 'child')
in the container and pseudo element is not used instead? There is not a reason to complicate the code. Also, more semantic in this case might be better.He’s already using psudos on the child itself
If you position the child absolute, top 0, left 0 and so son you can more easily control the Z stack. This comes in handy if you want to add layers with .sibling for image effects eg a vignette using a inner box shadow. You should also use visibility and opacity, not display none as u can’t animate/transition this. Follow me on Codepen @CodeBoomer and YouTube +Boomer, I’ll create an example in a few days when have time.
To achieve even better performance you can use
will-change:transform;
to move the child to its own GPU-layer. I did a quick demo, and frame-time during the actual animation is cut in half on my machine!+1
Awesome!
I prefer scaling from <1 to 1 instead of 1 to higher, so it doesn’t blur the image.
With CSS I guess it would be using a DIV to keep the image as fixed and always cover 100% of the display where shown. Of
course CSS and HTML is generated and sent from the server so you should resize the div by javascript.
Its probably nicer (more semantic) to use an actual image element, you get all the same control as a background image (using object-fit instead of background-size, and srcset for different sizes), only thing you would need to do is create the correct aspect ratio for the image (which you’ve already covered) https://css-tricks.com/snippets/sass/maintain-aspect-ratio-mixin/
Swap out that default linear transition for a cubic-bezier and hey presto, sexy zooms all round!
One possible (albeit hardly semantic) solution for mobile is to use hidden radio buttons and their associated labels: You hide the
input
, wrap thelabel
around what you want to show/hide, and then usethechecked
property (attribute?) to toggle between the states you want, no need for any kind of JavaScript. This page uses an example of this technique: http://coxlawpdx.com/results/Weaknesses: Makes zero semantic sense; no real way to deselect all items.
I prefer to animate the overlay and text as they appear so it isn’t quite as abrupt as switching between
display: none
anddisplay: block
. Here’s an example where I use thevisibility
andopacity
properties to achieve a smoother transition:I like that. Good addition.
+1
First of all, thanks to Dylan Winn-Brown for sharing with us this very nice effect.
Here is mine contribution:
I tried to use only two elements without any :before or :after pseudos. Text is originally set to “color: transparent;” and then with transition animated to “color: #f2f2f2;”.
With “background-blend-mode” and “background-color” in hover-state I created darker background.
Here is link to codepen: http://codepen.io/anon/pen/oLJaOB
Ok so I’ve created this pen: http://codepen.io/CodeBoomer/pen/ZOVPZb
I have:
– Injected the background-image with inline style to replicate JS as you wouldn’t declare this in CSS
– Created 3 children to the (parent element) because:
a) The image needs it’s own child node as you’re scaling this element on hover. By applying will-change, this isolates the transform to the image only
b) Any image filters can be utilised at the Pseudo elements (although will perform poorly at transform)
c) Hover color overlays don’t need to be transformed (I’ve applied a radial gradient and box-shaodw to create a vignette)
d) You can make up for accessibility with the name/description in it’s own node. Plus you can transform this with different timing for a semi perspective effect
e) Makes it easier to control stack order
Used visibility and opacity instead of display: none/block as display is not animateable
Created a simple flexbox layout to house the elements
Even though I forked the pen, I stripped it and rebuilt the entire thing and use SASS
Bloated the CSS code with flex properties to make it a bit easier to folllow
Created my own mixin for position absolute as who wants to write the long version.
Appreciate any feedback/critiques. Remember to follow me on Codepen and I’ll release a video for this soon on my Let’s Codepen series on YouTube. Subscribe to my channel https://www.youtube.com/channel/UCydykDsTWRIVnxKHW3SHPQA
Thanks for all the feedback guys – hope it helps some of you out if you ever need an effect like this.
Although it uses SASS, this code ^ seems to have the best performance out of the alternative methods that I have seen :)
My only change would be on the child pseudo element, instead of doing “display:none” and “display:block” why not just change the alpha to 0 and .75 and remove the display property?
.child::before {
content: “”;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
background-color: rgba(52, 73, 94, 0);
}
.parent:hover .child:before,
.parent:focus .child:before {
background-color: rgba(52, 73, 94, 0.75);
}
A thought on “mobile” support:
I use a small feature detection JS. If there is touch support, i replace the body class ‘no-touch’ with ‘has-touch’.
That gives me the ability to show the title permanently. So the user gets a better experience. In the case of your demo, the use doesn’t need to guess the location of the image.
Also i would integrate the suggestions of @praliedutzel and @Filip Bech-Larsen wich will get the demo close to perfect.