Fade To Black - Responsive CSS Gradients
Responsive design brings a fascinating array of challenges to both designers and developers. Using background images in a call to action or blockquote element is a great way to add visual appeal to a design, as you can see in the image to the left.
However, at mobile sizes, you’re faced with some tough decisions. Do you try and stretch the image to fit the height of the container? If so, at very tall/narrow widths, you’re forced to load a giant image, and it likely won’t be recognizable.
In addition, forcing mobile users to load a large image is bad for performance. Creating custom responsive image sets would work, but that sets up a maintenance problem, something most clients will not appreciate.
Luckily, there’s a solution that allows us to keep the image aspect ratio, set up standard responsive images, and it looks great on mobile as well. The fade-out!
I’ll be using screenshots and code here, but I’ve also made all 6 steps available on CodePen if you want to play with the code and try out different colors, images, etc…
Let’s start with that first blockquote:
(pen) This is set up for desktop - the image aspect ratio is determining the height of the container using the padding ratio trick. Everything in the container is using absolute positioning and flexbox for centering. We have a simple rgba()
background set using the :before
pseudo-property in the .parent-container:
<span class="nd">:before</span> <span class="p">{</span> <span class="nl">content</span><span class="p">:</span> <span class="s1">""</span><span class="p">;</span> <span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span> <span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span> <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span> <span class="nl">height</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span> <span class="nl">background-color</span><span class="p">:</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">0.4</span><span class="p">);</span> <span class="nl">z-index</span><span class="p">:</span> <span class="m">10</span><span class="p">;</span> <span class="nl">top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span>
(pen) The issues arise once we get a quote of reasonable length, and/or the page width gets too small. As you can see, it overflows and breaks quite badly.
(pen) We can fix this by setting some changes to take place at a certain breakpoint, depending on the max length of the field and the size of the image used.
Specifically, we remove the padding from the parent element, and make the .content-wrapper position: static
. (I like to set a min-height as well just in case the content is very small)
(pen) Now we can add the fader code - background-image: linear-gradient
, which can be used unprefixed. This is inserted into the .image-wrapper using another :before
pseudo-element:
<span class="nd">:before</span> <span class="p">{</span> <span class="nl">content</span><span class="p">:</span> <span class="s1">""</span><span class="p">;</span> <span class="nl">display</span><span class="p">:</span> <span class="n">inline-block</span><span class="p">;</span> <span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span> <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span> <span class="nl">height</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span> <span class="nl">background-image</span><span class="p">:</span> <span class="n">linear-gradient</span><span class="p">(</span> <span class="p">//</span> <span class="n">Fade</span> <span class="n">over</span> <span class="n">the</span> <span class="n">entire</span> <span class="n">image</span> <span class="n">-</span> <span class="n">not</span> <span class="n">great</span><span class="p">.</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.0</span><span class="p">)</span> <span class="m">0%</span><span class="p">,</span> <span class="n">rgba</span><span class="p">(</span><span class="m">255</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">1.0</span><span class="p">)</span> <span class="m">100%</span> <span class="p">);</span> <span class="p">}</span><span class="o">;</span>
(pen) The issue now is that the gradient covers the entire image, but we can fix that easily by adding additional rgba()
values, in effect ‘stretching’ the part of the gradient that’s transparent:
<span class="nd">:before</span> <span class="p">{</span> <span class="nl">background-image</span><span class="p">:</span> <span class="n">linear-gradient</span><span class="p">(</span> <span class="p">//</span> <span class="n">Transparent</span> <span class="n">at</span> <span class="n">the</span> <span class="nb">top</span><span class="p">.</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.0</span><span class="p">)</span> <span class="m">0%</span><span class="p">,</span> <span class="p">//</span> <span class="n">Still</span> <span class="nb">transparent</span> <span class="n">through</span> <span class="m">70%</span> <span class="n">of</span> <span class="n">the</span> <span class="n">image</span><span class="p">.</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.0</span><span class="p">)</span> <span class="m">70%</span><span class="p">,</span> <span class="p">//</span> <span class="n">Now</span> <span class="n">fade</span> <span class="n">to</span> <span class="nb">solid</span> <span class="n">to</span> <span class="n">match</span> <span class="n">the</span> <span class="n">background</span><span class="p">.</span> <span class="n">rgba</span><span class="p">(</span><span class="m">255</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">1.0</span><span class="p">)</span> <span class="m">100%</span> <span class="p">);</span> <span class="p">}</span>
(pen) Finally, we can fine-tune the gradient by adding even more rgba()
values and setting the percentages and opacity as appropriate.
Once we’re satisfied that the gradient matches the design, all that’s left is to make the gradient RGBA match the .parent-container background color (not the overlay - this tripped me up for a while!), which in our case is supposed to be #000
:
<span class="nd">:before</span> <span class="p">{</span> <span class="nl">background-image</span><span class="p">:</span> <span class="n">linear-gradient</span><span class="p">(</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.0</span><span class="p">)</span> <span class="m">0%</span><span class="p">,</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.0</span><span class="p">)</span> <span class="m">70%</span><span class="p">,</span> <span class="p">//</span> <span class="n">These</span> <span class="n">three</span> <span class="s2">'smooth'</span> <span class="n">out</span> <span class="n">the</span> <span class="n">fade</span><span class="p">.</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.2</span><span class="p">)</span> <span class="m">80%</span><span class="p">,</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.7</span><span class="p">)</span> <span class="m">90%</span><span class="p">,</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.9</span><span class="p">)</span> <span class="m">95%</span><span class="p">,</span> <span class="p">//</span> <span class="n">Solid</span> <span class="n">to</span> <span class="n">match</span> <span class="n">the</span> <span class="n">background</span><span class="p">.</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">1.0</span><span class="p">)</span> <span class="m">100%</span> <span class="p">);</span> <span class="p">}</span>
We’ll be rolling out sites in a few weeks with these techniques in live code, and with several slight variations to the implementation (mostly adding responsive images and making allowances for Drupal’s markup), but this is the core idea used.
Feel free to play with the code yourself, and change the rgba()
values so that you can see what each is doing.