Reference
I started by collecting images of holograms shown in movies, tv shows and music videos. Properties used to communicate something was a holograms were:
- ▪ only 1 hue (blue or green),
- ▪ only show an image's subject (no background),
- ▪ a faded triangle positioned beneath the subject indicating the hologram's source,
- ▪ scan lines (with a distortion animation) &
- ▪ a blur around the edges.


Prepare Asset
The hologram's subject will be taken from the above image of Gwen Stacy. Erase.bg was used to remove the background.

Render the Image
To reduce complexity, everything will be rendered within an <svg>
element.
The <image>
element is used to render the subject's .png
file.
localhost:3000
<svg viewBox="0 0 300 300"> <image href="https://garden.bradwoods.io/images/gwenStacy.png" width="280" x="10" y="0" /> </svg>
Blue
To make the subject blue, an SVG filter with the <feComponentTransfer>
primitive is used.
This primitive remaps the color of each pixel.
If your unfamiliar with SVG filters, you can find a note explaining the fundamentals here.
localhost:3000
<svg viewBox="0 0 300 300"> <defs> <filter id="myImgFilter"> <!-- Make the source image blue. --> <feComponentTransfer in="SourceGraphic" result="BLUE_RESULT"> <feFuncR type="linear" slope="0"></feFuncR> <feFuncG type="linear" slope="2"></feFuncG> <feFuncB type="linear" slope="20"></feFuncB> </feComponentTransfer> </filter> </defs> <image filter="url(#myImgFilter)" href="https://garden.bradwoods.io/images/gwenStacy.png" width="280" x="10" y="0" /> </svg>
Blur
The blur effect can be created by adding more primitives to the filter.
We can take the result from <feComponentTransfer>
and:
- ▪ apply a Gaussian blur,
- ▪ reduce the opacity &
- ▪ translate it to the right (or left).
The output of the filter is the combination of 3 images:
- ▪ the blue result,
- ▪ the blurred, reduced opacity and translate left result and
- ▪ the blurred, reduced opacity and translate right result.
localhost:3000
<svg viewBox="0 0 300 300"> <defs> <filter id="myImgFilter" width="300%" height="300%" x="-100%" y="-100%"> <!-- Make the source image blue. --> <feComponentTransfer in="SourceGraphic" result="BLUE_RESULT"> <feFuncR type="linear" slope="0"></feFuncR> <feFuncG type="linear" slope="2"></feFuncG> <feFuncB type="linear" slope="20"></feFuncB> </feComponentTransfer> <!-- Blur the blue result (blur in x-direction only). --> <feGaussianBlur stdDeviation="4, 0" edgeMode="none" in="BLUE_RESULT" result="BLUR_RESULT" /> <!-- Reduce the opacity of the blur result. --> <!-- slope = any number, C' = slope * C + intercept --> <feComponentTransfer in="BLUR_RESULT" result="OPACITY_RESULT"> <feFuncA type="linear" slope="0.3" /> </feComponentTransfer> <!-- Translate the reduced opacity result to the left. --> <feOffset dx="-8" dy="0" in="OPACITY_RESULT" result="OFFSET_LEFT_RESULT" /> <!-- Translate the reduced opacity result to the right. --> <feOffset dx="8" dy="0" in="OPACITY_RESULT" result="OFFSET_RIGHT_RESULT" /> <!-- Combine the blue result and 2 translated results. --> <feMerge result="COMBINE_RESULT"> <feMergeNode in="OFFSET_LEFT_RESULT" /> <feMergeNode in="OFFSET_RIGHT_RESULT" /> <feMergeNode in="BLUE_RESULT" /> </feMerge> </filter> </defs> <image filter="url(#myImgFilter)" href="https://garden.bradwoods.io/images/gwenStacy.png" width="280" x="10" y="0" /> </svg>
Scan Lines
Scan lines can be created using the <pattern>
element to create a series of repeating white horizontal lines.
That pattern can be used wihin a <mask>
and the mask applied to a <g>
element.
When the <image>
is wrapped in the <g>
element, only pixels from the <image>
that overlap with white pixels from the mask will be visible.
This result in a series of alternating lines going down the page:
image pixels -> transparent -> image pixels -> transparent -> ...
If you are unfamiliar with SVG patterns or masks, you can find notes explaining the fundamentals here:
localhost:3000
<svg viewBox="0 0 300 300"> <defs> <filter id="myImgFilter" width="300%" height="300%" x="-100%" y="-100%"> <!-- Make the source image blue. --> <feComponentTransfer in="SourceGraphic" result="BLUE_RESULT"> <feFuncR type="linear" slope="0"></feFuncR> <feFuncG type="linear" slope="2"></feFuncG> <feFuncB type="linear" slope="20"></feFuncB> </feComponentTransfer> <!-- Blur the blue result (blur in x-direction only). --> <feGaussianBlur stdDeviation="4, 0" edgeMode="none" in="BLUE_RESULT" result="BLUR_RESULT" /> <!-- Reduce the opacity of the blur result. --> <!-- slope = any number, C' = slope * C + intercept --> <feComponentTransfer in="BLUR_RESULT" result="OPACITY_RESULT"> <feFuncA type="linear" slope="0.3" /> </feComponentTransfer> <!-- Translate the reduced opacity result to the left. --> <feOffset dx="-8" dy="0" in="OPACITY_RESULT" result="OFFSET_LEFT_RESULT" /> <!-- Translate the reduced opacity result to the right. --> <feOffset dx="8" dy="0" in="OPACITY_RESULT" result="OFFSET_RIGHT_RESULT" /> <!-- Combine the blue result and 2 translated results. --> <feMerge result="COMBINE_RESULT"> <feMergeNode in="OFFSET_LEFT_RESULT" /> <feMergeNode in="OFFSET_RIGHT_RESULT" /> <feMergeNode in="BLUE_RESULT" /> </feMerge> </filter> <!-- A pattern of horizontal lines going down the page. --> <pattern id="myPattern" width="10" height="2" viewBox="0 0 10 2" patternUnits="userSpaceOnUse"> <!-- Must be set to white for <mask>. --> <line x1="0" y="0" x2="10" y2="0" stroke="white" stroke-width="3" /> </pattern> <mask id="myScanLinesMask"> <!-- A rectangle filled with the above pattern. --> <rect x="0" y="0" width="100%" height="100%" fill="url(#myPattern)" /> </mask> </defs> <g mask="url(#myScanLinesMask)"> <image filter="url(#myImgFilter)" href="https://garden.bradwoods.io/images/gwenStacy.png" width="280" x="10" y="0" /> </g> </svg>
Triangle
To render the triangle below the subject, the <polygon>
element is used.
localhost:3000
<svg viewBox="0 0 300 300"> <defs> <filter id="myImgFilter" width="300%" height="300%" x="-100%" y="-100%"> <!-- Make the source image blue. --> <feComponentTransfer in="SourceGraphic" result="BLUE_RESULT"> <feFuncR type="linear" slope="0"></feFuncR> <feFuncG type="linear" slope="2"></feFuncG> <feFuncB type="linear" slope="20"></feFuncB> </feComponentTransfer> <!-- Blur the blue result (blur in x-direction only). --> <feGaussianBlur stdDeviation="4, 0" edgeMode="none" in="BLUE_RESULT" result="BLUR_RESULT" /> <!-- Reduce the opacity of the blur result. --> <!-- slope = any number, C' = slope * C + intercept --> <feComponentTransfer in="BLUR_RESULT" result="OPACITY_RESULT"> <feFuncA type="linear" slope="0.3" /> </feComponentTransfer> <!-- Translate the reduced opacity result to the left. --> <feOffset dx="-8" dy="0" in="OPACITY_RESULT" result="OFFSET_LEFT_RESULT" /> <!-- Translate the reduced opacity result to the right. --> <feOffset dx="8" dy="0" in="OPACITY_RESULT" result="OFFSET_RIGHT_RESULT" /> <!-- Combine the blue result and 2 translated results. --> <feMerge result="COMBINE_RESULT"> <feMergeNode in="OFFSET_LEFT_RESULT" /> <feMergeNode in="OFFSET_RIGHT_RESULT" /> <feMergeNode in="BLUE_RESULT" /> </feMerge> </filter> <!-- A pattern of horizontal lines going down the page. --> <pattern id="myPattern" width="10" height="2" viewBox="0 0 10 2" patternUnits="userSpaceOnUse"> <!-- Must be set to white for <mask>. --> <line x1="0" y="0" x2="10" y2="0" stroke="white" stroke-width="3" /> </pattern> <mask id="myScanLinesMask"> <!-- A rectangle filled with the above pattern. --> <rect x="0" y="0" width="100%" height="100%" fill="url(#myPattern)" /> </mask> </defs> <g mask="url(#myScanLinesMask)"> <image filter="url(#myImgFilter)" href="https://garden.bradwoods.io/images/gwenStacy.png" width="280" x="10" y="0" /> <polygon fill="hsla(174, 85%, 77%, 1)" points="20,170 150,300 280,170" /> </g> </svg>
Fade
We now need to add a fade effect that moves from the bottom to the top.
The triangle should fade out and the subject should fade in.
This can be achieved with an opacity mask.
A white gradient of varying opacity rendered within a <mask>
.
localhost:3000
<svg viewBox="0 0 300 300"> <defs> <filter id="myImgFilter" width="300%" height="300%" x="-100%" y="-100%"> <!-- Make the source image blue. --> <feComponentTransfer in="SourceGraphic" result="BLUE_RESULT"> <feFuncR type="linear" slope="0"></feFuncR> <feFuncG type="linear" slope="2"></feFuncG> <feFuncB type="linear" slope="20"></feFuncB> </feComponentTransfer> <!-- Blur the blue result (blur in x-direction only). --> <feGaussianBlur stdDeviation="4, 0" edgeMode="none" in="BLUE_RESULT" result="BLUR_RESULT" /> <!-- Reduce the opacity of the blur result. --> <!-- slope = any number, C' = slope * C + intercept --> <feComponentTransfer in="BLUR_RESULT" result="OPACITY_RESULT"> <feFuncA type="linear" slope="0.3" /> </feComponentTransfer> <!-- Translate the reduced opacity result to the left. --> <feOffset dx="-8" dy="0" in="OPACITY_RESULT" result="OFFSET_LEFT_RESULT" /> <!-- Translate the reduced opacity result to the right. --> <feOffset dx="8" dy="0" in="OPACITY_RESULT" result="OFFSET_RIGHT_RESULT" /> <!-- Combine the blue result and 2 translated results. --> <feMerge result="COMBINE_RESULT"> <feMergeNode in="OFFSET_LEFT_RESULT" /> <feMergeNode in="OFFSET_RIGHT_RESULT" /> <feMergeNode in="BLUE_RESULT" /> </feMerge> </filter> <!-- A pattern of horizontal lines going down the page. --> <pattern id="myPattern" width="10" height="2" viewBox="0 0 10 2" patternUnits="userSpaceOnUse"> <!-- Must be set to white for <mask>. --> <line x1="0" y="0" x2="10" y2="0" stroke="white" stroke-width="3" /> </pattern> <mask id="myScanLinesMask"> <!-- A rectangle filled with the above pattern. --> <rect x="0" y="0" width="100%" height="100%" fill="url(#myPattern)" /> </mask> <!-- Fade gradient. --> <linearGradient id="myFadeGradient" gradientTransform="rotate(90)"> <stop offset="20%" stop-color="white" stop-opacity="1" /> <stop offset="58%" stop-color="white" stop-opacity="0" /> <stop offset="100%" stop-color="white" stop-opacity="1" /> </linearGradient> <mask id="myFadeMask"> <rect x="0" y="0" width="100%" height="100%" fill="url(#myFadeGradient)" /> </mask> </defs> <g mask="url(#myFadeMask)"> <g mask="url(#myScanLinesMask)"> <image filter="url(#myImgFilter)" href="https://garden.bradwoods.io/images/gwenStacy.png" width="280" x="10" y="0" /> <polygon fill="hsla(174, 85%, 77%, 1)" points="20,170 150,300 280,170" /> </g> </g> </svg>
Accessibility
The last thing we need to do is address accessibility.
Adding a <title>
with a description of the image.
Screen readers will be able to read this text and communicate what the image is for visually impaired users.
localhost:3000
<svg viewBox="0 0 300 300"> <defs> <filter id="myImgFilter" width="300%" height="300%" x="-100%" y="-100%"> <!-- Make the source image blue. --> <feComponentTransfer in="SourceGraphic" result="BLUE_RESULT"> <feFuncR type="linear" slope="0"></feFuncR> <feFuncG type="linear" slope="2"></feFuncG> <feFuncB type="linear" slope="20"></feFuncB> </feComponentTransfer> <!-- Blur the blue result (blur in x-direction only). --> <feGaussianBlur stdDeviation="4, 0" edgeMode="none" in="BLUE_RESULT" result="BLUR_RESULT" /> <!-- Reduce the opacity of the blur result. --> <!-- slope = any number, C' = slope * C + intercept --> <feComponentTransfer in="BLUR_RESULT" result="OPACITY_RESULT"> <feFuncA type="linear" slope="0.3" /> </feComponentTransfer> <!-- Translate the reduced opacity result to the left. --> <feOffset dx="-8" dy="0" in="OPACITY_RESULT" result="OFFSET_LEFT_RESULT" /> <!-- Translate the reduced opacity result to the right. --> <feOffset dx="8" dy="0" in="OPACITY_RESULT" result="OFFSET_RIGHT_RESULT" /> <!-- Combine the blue result and 2 translated results. --> <feMerge result="COMBINE_RESULT"> <feMergeNode in="OFFSET_LEFT_RESULT" /> <feMergeNode in="OFFSET_RIGHT_RESULT" /> <feMergeNode in="BLUE_RESULT" /> </feMerge> </filter> <!-- A pattern of horizontal lines going down the page. --> <pattern id="myPattern" width="10" height="2" viewBox="0 0 10 2" patternUnits="userSpaceOnUse"> <!-- Must be set to white for <mask>. --> <line x1="0" y="0" x2="10" y2="0" stroke="white" stroke-width="3" /> </pattern> <mask id="myScanLinesMask"> <!-- A rectangle filled with the above pattern. --> <rect x="0" y="0" width="100%" height="100%" fill="url(#myPattern)" /> </mask> <!-- Fade gradient. --> <linearGradient id="myFadeGradient" gradientTransform="rotate(90)"> <stop offset="20%" stop-color="white" stop-opacity="1" /> <stop offset="58%" stop-color="white" stop-opacity="0" /> <stop offset="100%" stop-color="white" stop-opacity="1" /> </linearGradient> <mask id="myFadeMask"> <rect x="0" y="0" width="100%" height="100%" fill="url(#myFadeGradient)" /> </mask> </defs> <title>A hologram of a superhero crouching</title> <g mask="url(#myFadeMask)"> <g mask="url(#myScanLinesMask)"> <image filter="url(#myImgFilter)" href="https://garden.bradwoods.io/images/gwenStacy.png" width="280" x="10" y="0" /> <polygon fill="hsla(174, 85%, 77%, 1)" points="20,170 150,300 280,170" /> </g> </g> </svg>