Left ArrowBack

notes / SVG / examples / hologram

hologram

A hologram of a superhero.

SVG Hologram

Planted

Status: seed

1. Reference

I started by collecting images of holograms shown in movies, tv shows & 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.
Images of holograms.
A superhero with background scenary.

2. Prepare Asset

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

A superhero
A hologram of a superhero.

3. 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

Console

<svg viewBox="0 0 300 300">
    <image
        href="https://garden.bradwoods.io/images/gwenStacy.png" 
        width="280" 
        x="10"
        y="0" />
</svg>

A hologram of a superhero.

4. 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

Console

<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>

A hologram of a superhero.

5. 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 & translate left result &
  • the blurred, reduced opacity & translate right result.

localhost:3000

Console

<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 & 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>

A hologram of a superhero.

6. 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> & 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

Console

<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 & 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>

A hologram of a superhero.

7. Triangle

To render the triangle below the subject, the <polygon> element is used.

localhost:3000

Console

<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 & 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>

A hologram of a superhero.

8. 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

Console

<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 & 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>

The internationally recognized symbol for accessibility.

9. 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 & communicate what the image is for visually impaired users.

localhost:3000

Console

<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 & 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>

Where to Next?

A sci-fi robot taxi driver with no lower body