I’ve long wanted to recreate the effect of the sunglasses used in the classic John Carpenter film They Live: putting on the sunglasses would reveal the subliminal messages encoded in all forms of media by a secret alien conspiracy. I successfully created the effect using SVG masks and mouse tracking, which you can see in the example above.

An Image Sandwich

The key to the technique is layering two versions of the same image - an ordinary full-color photograph, and a black-and-white version retouched in . (I could have also used a black-and-white filter in SVG for the second image, potentially reducing its size by saving only the billboards as a transparent PNG).

Embedded in , the image code looks like this:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
viewBox="0 0 1500 1122">
 <image xlink:href="they-live-color.jpg" x="0" y="0" height="100%" width="100%" /> 
 <image xlink:href="they-live-bw.jpg" x="0" y="0" height="100%" width="100%" /> 
</svg>

Note that the black-and-white version is on top of the color version, and that the SVG element itself is sized to the pixel dimensions of the (paired) images; the images themselves are sized to take up 100% of this space, making them (and the SVG as a whole) responsive.

The original and retouched photographs

And Those Wayfarers On, Baby

The sunglasses are divided into three parts: two paths describing the left and right lenses, and another path forming the frame. The lenses are placed inside a <clipPath>, the frame, with an id of wayfarers, is placed last in the SVG:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1500 1122">
  <defs>
    <clipPath id="lenses" transform="scale(2)"> 
    <path d="M312.4,28c-15.8,6.9-23.9,24.1-23.9,32.3 …" />
    <path d="M185.6,28c-24.6-10.8-92.2-12.1-114.9-3.6 …" />
    </clipPath> 
  </defs>
 <image xlink:href="they-live-color.jpg" x="0" y="0" height="100%" width="100%" /> 
 <image xlink:href="they-live-bw.jpg" x="0" y="0" height="100%" width="100%" /> 
 <path id="wayfarers" transform="matrix(2 0 0 2 0 0)" 
d="M355.5,6.4c-44.7,2.1-84,22.2-107.7,22.7C223.7,29.7,198.6,11,140.1,6.4 … "/>
</svg>

Both the lenses and the frames need to be twice as large as they are naturally, which is the reason for the transnform on each.

Filtering the World

To apply the lenses as a clip to the black-and-white image, we address the clipPath from there. The code for the second image is altered to:

<image xlink:href="they-live-bw.jpg" x="0" y="0" height="100%" width="100%" 
clip-path="url(#lenses)" /> 

A little CSS is applied also:

body { margin: 0; }
#wayfarers { cursor: move; }

Mouse Tracking in SVG

Adding a script to the bottom of the SVG, we start by identifying the elements that need to be tracked and controlled, together with creating a single, unseen point:

var svgElement = document.querySelector("svg"),
lenses = document.querySelector("#lenses"),
wayfarers = document.querySelector("#wayfarers"),
svgPoint = svgElement.createSVGPoint();

This invisible point is tracked in a function that matches the position of the mouse cursor; using a matrixTransform, we determine the position of the mouse in SVG space:

function cursorPoint(e, svg) {
    svgPoint.x = e.clientX;
    svgPoint.y = e.clientY;
    return svgPoint.matrixTransform(svg.getScreenCTM().inverse());
}

The position of the sunglasses and its lenses are determined with another function: finding the width and height of the sunglasses, determining its center, relative to the mouse position, and moving each with a transform, while retaining their scale:

function update(svgCoords) {
    var wayWidth = wayfarers.getBoundingClientRect().width,
    wayHeight = wayfarers.getBoundingClientRect().height,
    coos = ((svgCoords.x / 2) - wayWidth / 2) + " " + ((svgCoords.y / 2) - wayHeight / 2);
    lenses.setAttribute("transform", "scale(2) translate("+coos+")");
    wayfarers.setAttribute("transform", "scale(2) translate("+coos+")");
}

Mouse movement and touches on a mobile device are covered by two eventListeners:

 svgElement.addEventListener("mousemove", function(e) {
  update(cursorPoint(e, svgElement));
}, false);

svgElement.addEventListener("touchmove", function(e) {
    e.preventDefault();
    var touch = e.targetTouches[0];
    if (touch) {
        update(cursorPoint(touch, svgElement));
    }
}, false);

You can find the complete code in the associated CodePen demo; I’ll be showing variations of this technique in future articles.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/LkALRw