Pseudo-elements and the image tag
Logic is a funny thing. I had this image of a bottle which would look more natural on a web page if a shadow was cast below the bottle. Not a box-shadow, mind you. The bottle was an irregular thing with transparency to boot so I fetched a pseudo-element from the CSS shelve and set to work on the image tag. No luck. Or more to the point: No logic. Granted, I do not overuse ::before
and ::after
on CSS selectors so I might be rusty in that department, but hours with nothing but nada to show? I know now that I should have visited Stack Overflow at an earlier stage but stubbornness gained the upper hand. To cut a long story short, however, here is the note from the mothership:
This specification does not fully define the interaction of :before
and :after
with replaced elements (such as IMG in HTML). This will be defined in more detail in a future specification.
Deciphered, this leaves it to each browser vendor to implement or not. Few have done so. The future is, as always, not here and now.
With the understanding that this is rocket science, I picked up the blanket from where I had thrown it (I had flung the bottle at the same time – it used to be a glass bottle) and decided to launch my own rocket, the wishful one below, to test different scenarios.
Wishful thinking
Fig 1. The image above is an SVG including both a rocket and its muzzle flame. As it stands it is only a visualization of what I want to achieve and should render equally in all browsers – in all browsers that support the SVG format, that is.
First, however, the definition
Pseudo-elements like ::before
and ::after
should not be confused with pseudo-classes like :link
. The latter are similar to classes in HTML, but they are not specified explicitly in the markup. Pseudo-elements match virtual elements that do not exist explicitly in the document tree. ::before
– or ::after
– represents generated content rendered before – or after – another element. A pseudo-element is used in conjunction with the content
property, and additional properties can be specified to style the element. The generated content is only rendered – it does not become part of the document tree.
Every browser that supports the double colon (::
) CSS syntax also supports the single colon (:
) syntax. The double colon is a newer format introduced to distinguish pseudo content from pseudo selectors. Bear in mind, however, that Internet Explorer 8 (what?) only supports the single colon.
The litmus test
Before we delve into the problem at hand, let us check if our browser supports pseudo-elements at all.
<p id="litmus">Main</p>
#litmus::before {
color: #fc0;
content: "Before";
}
#litmus::after {
color: #900;
content: "After";
}
The result:
Main
If the result reads BeforeMainAfter, with Before in yellow and After in red, our browser is good to go, and if we try to select the text, we will find that only Main is selectable. Quad erat demonstrandum.
Rocket et al.
The proper thing would be to test both a ::before
element and an ::after
element on our main image and that is exactly what we will do here. A big hand, then, for the three parts needed to build and launch the wishful rocket:
Fig 2. Left: Yellow muzzle flame. This will be the “before” element. Middle: Our own mothership. This is the main image. Right: Red muzzle flame. This will be the “after” element; the afterburner, so to speak. (Sorry, I could not resist that one.) Dotted outlines indicate image sizes – which are equal for this exercise.
We are now ready to test different methods.
The img
tag
<img id="imgTag" src="rocket.svg">
#imgTag {
position: relative;
width: 80px;
}
#imgTag::before {
position: absolute;
width: 80px;
content: url("before.svg");
}
#imgTag::after {
position: absolute;
width: 80px;
content: url("after.svg");
}
The result:
Fig. 3. If you see a rocket with muzzle flame like the one in figure 1, you have lift-off and are part of the future. If you see only the rocket, you are in good company regardless; I am grounded here in Safari 10.0.3.
A generic box
Let us forget about the rocket for a moment then and concentrate on that old workhorse, the div
tag.
<div id="divTag"></div>
#divTag {
position: relative;
width: 80px; /* Matches width of images */
height: 152px; /* Matches height of images */
background-color: rgba(0,0,0,.24);
}
#divTag::before {
position: absolute;
width: 80px;
content: url("before.svg");
}
#divTag::after {
position: absolute;
width: 80px;
content: url("after.svg");
}
The result:
Fig. 4. No parent image here. The rocket has been replaced by a partly transparent, rectangular box. The before and after elements should be in place. Note that, owing to the fact that the images in this exercise match the size of the parent box, absolute positioning can default to top/left.
Wrapped in a container
So, if a generic box accepts the pseudo-elements, why not wrap the rocket in a container?
<div id="imgContainer"><img src="rocket.svg"></div>
#imgContainer { position: relative; }
img { width: 80px; }
#imgContainer::before {
position: absolute;
width: 80px;
content: url("before.svg");
}
#imgContainer::after {
position: absolute;
top: 0;
left: 0;
width: 80px;
content: url("after.svg");
}
The result:
Fig. 5. Yes, this small “hack” does a nice job. Note that it is necessary to absolutely position the after element. Otherwise it would be positioned after the main image and not after the container.
Background image
Depending on the circumstances, it is possible to use the rocket as a background image for the container thus avoiding extra markup. Note that the dimensions of the image might need to be known.
<div id="bgImage"></div>
#bgImage {
position: relative;
width: 80px;
height: 152px;
background-image: url("rocket.svg");
}
#bgImage::before {
position: absolute;
width: 80px;
content: url("before.svg");
}
#bgImage::after {
position: absolute;
width: 80px;
content: url("after.svg");
}
The result:
Fig. 6. It just works.
Directly on the svg
tag?
Just curious: What happens if the pseudo-elements are set up directly on the SVG code, i.e. replacing the main image tag with an svg
tag?
<svg … >…gibberish…</svg>
svg {
position: relative;
width: 80px;
}
svg::before {
position: absolute;
width: 80px;
content: url("before.svg");
}
svg::after {
position: absolute;
width: 80px;
content: url("after.svg");
}
The result:
Fig. 7. Oh, well. Hope springs eternal. It is my guess that inline SVG is treated as a replaced element, i.e. an element whose content is outside the scope of the CSS formatting model, such as an image.
A JavaScript solution?
With the jQuery library already implemented, it would be sweet to be able to manipulate pseudo-elements with JavaScript. And believe me, I have searched high and low but I have not been able to find anything useful in that department. It boils down to the definition and its consequence:
Pseudo-elements match virtual elements that do not exist explicitly in the document tree. For tecnical reasons they can therefore not be altered with JavaScript, and hence jQuery, DOM methods.
Yes, jQuery sports the before()
and after()
methods, but those do not work like pseudo-elements. You could easily whip up a function like so:
$(function(){
$("#someImage").before("<img src='before.svg'>");
$("#someImage").after("<img src='after.svg'>");
});
But the result would be more or less akin to figure 2; a sequence of separate images.
Old soldiers never die, they only fade away. Count me in.
Notes
On the use of SVG images: They may not render in all browsers but when they do (based on how they are constructed for this demonstration) they should not clutter the CSS code with stuff necessary to fake bitmap flames. And, of course, being vectors, they can be scaled at will and they keep their proportions.
On stacking order: The example images used in this article are without solid backgrounds. With different images it may be necessary to adjust z-indices accordingly.
References
1.
Christopher Harris on Stack Overflow2.
Colin Seymour, :after CSS Property For IMG Tag3.
Tommy Olsson and Paul O’Brien, The Ultimate CSS Reference, SitePoint (2008)4.
Treehouse CSS-Tricks5.
Christian Krammer’s CSS3 files. This web site is extinct.