Component: Product Mockup

Implementation Notes

Image size for Screenshots

Desktop Monitor:

  • Optimum: Full HD (1920×1080 pixels, 16:9 aspect ratio)
  • Minimum: 960×540 pixels, 16:9 aspect ratio

Tablet:

  • Optimum: QuadVGA (1280×960 pixels, 4:3 aspect ratio)
  • Minimum: 1024×768 pixels, 4:3 aspect ratio
<!-- Default -->
<div class="productMockup">

    <div class="productMockup-figure">
        <figure class="figure" style="">

            <a href="../../#.html">
                <img class="figure-image" src="../../images/productMockup/sample-screenshot-desktop.png" alt="Ich bin eine Produktabbildung" />

            </a>

            <figcaption class="figure-caption"><span>Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.</span></figcaption>

        </figure>

    </div>

</div>

<!-- Three Quarter -->
<div class="productMockup" data-view="three-quarter">

    <div class="productMockup-figure">
        <figure class="figure" style="">

            <a href="../../#.html">
                <img class="figure-image" src="../../images/productMockup/sample-screenshot-desktop.png" alt="Ich bin eine Produktabbildung" />

            </a>

            <figcaption class="figure-caption"><span>Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.</span></figcaption>

        </figure>

    </div>

</div>

<!-- Tablet -->
<div class="productMockup" data-device="tablet">

    <div class="productMockup-figure">
        <figure class="figure" style="">

            <a href="../../#.html">
                <img class="figure-image" src="../../images/productMockup/sample-screenshot-tablet.png" alt="Ich bin eine Produktabbildung" />

            </a>

            <figcaption class="figure-caption"><span>Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.</span></figcaption>

        </figure>

    </div>

</div>

<!-- Tablet Three Quarter -->
<div class="productMockup" data-device="tablet" data-view="three-quarter">

    <div class="productMockup-figure">
        <figure class="figure" style="">

            <a href="../../#.html">
                <img class="figure-image" src="../../images/productMockup/sample-screenshot-tablet.png" alt="Ich bin eine Produktabbildung" />

            </a>

            <figcaption class="figure-caption"><span>Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.</span></figcaption>

        </figure>

    </div>

</div>

<!-- Documentation only -->
<section class="doc-variant">

    <header class="doc-variant-header">

        <h2 class="doc-variant-title">Desktop Front View (Default)</h2>

        <div class="doc-variant-configuration">
        </div>

        <div class="doc-variant-configuration">
            <label>
                Theme:
                <select class="doc-select" onchange="fractal.setTheme(this, {target: '.doc-variant-samples', value: this.value});">
                    <option value="default">default</option>
                    <option value="accent">accent</option>
                    <option value="dark">dark</option>
                </select>
            </label>
        </div>

    </header>

    <div class="doc-variant-samples" data-theme="" style="overflow: hidden;">

        <div class="productMockup">

            <div class="productMockup-figure">
                <figure class="figure" style="">

                    <a href="../../#.html">
                        <img class="figure-image" src="../../images/productMockup/sample-screenshot-desktop.png" alt="Ich bin eine Produktabbildung" />

                    </a>

                    <figcaption class="figure-caption"><span>Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.</span></figcaption>

                </figure>

            </div>

        </div>

    </div>

</section>
<section class="doc-variant">

    <header class="doc-variant-header">

        <h2 class="doc-variant-title">Desktop Three-quarter View</h2>

        <div class="doc-variant-configuration">
        </div>

        <div class="doc-variant-configuration">
            <label>
                Theme:
                <select class="doc-select" onchange="fractal.setTheme(this, {target: '.doc-variant-samples', value: this.value});">
                    <option value="default">default</option>
                    <option value="accent">accent</option>
                    <option value="dark">dark</option>
                </select>
            </label>
        </div>

    </header>

    <div class="doc-variant-samples" data-theme="" style="overflow: hidden;">

        <div class="productMockup" data-view="three-quarter">

            <div class="productMockup-figure">
                <figure class="figure" style="">

                    <a href="../../#.html">
                        <img class="figure-image" src="../../images/productMockup/sample-screenshot-desktop.png" alt="Ich bin eine Produktabbildung" />

                    </a>

                    <figcaption class="figure-caption"><span>Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.</span></figcaption>

                </figure>

            </div>

        </div>

    </div>

</section>
<section class="doc-variant">

    <header class="doc-variant-header">

        <h2 class="doc-variant-title">Tablet Front View</h2>

        <div class="doc-variant-configuration">
        </div>

        <div class="doc-variant-configuration">
            <label>
                Theme:
                <select class="doc-select" onchange="fractal.setTheme(this, {target: '.doc-variant-samples', value: this.value});">
                    <option value="default">default</option>
                    <option value="accent">accent</option>
                    <option value="dark">dark</option>
                </select>
            </label>
        </div>

    </header>

    <div class="doc-variant-samples" data-theme="" style="overflow: hidden;">

        <div class="productMockup" data-device="tablet">

            <div class="productMockup-figure">
                <figure class="figure" style="">

                    <a href="../../#.html">
                        <img class="figure-image" src="../../images/productMockup/sample-screenshot-tablet.png" alt="Ich bin eine Produktabbildung" />

                    </a>

                    <figcaption class="figure-caption"><span>Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.</span></figcaption>

                </figure>

            </div>

        </div>

    </div>

</section>
<section class="doc-variant">

    <header class="doc-variant-header">

        <h2 class="doc-variant-title">Tablet Three-quarter View</h2>

        <div class="doc-variant-configuration">
        </div>

        <div class="doc-variant-configuration">
            <label>
                Theme:
                <select class="doc-select" onchange="fractal.setTheme(this, {target: '.doc-variant-samples', value: this.value});">
                    <option value="default">default</option>
                    <option value="accent">accent</option>
                    <option value="dark">dark</option>
                </select>
            </label>
        </div>

    </header>

    <div class="doc-variant-samples" data-theme="" style="overflow: hidden;">

        <div class="productMockup" data-device="tablet" data-view="three-quarter">

            <div class="productMockup-figure">
                <figure class="figure" style="">

                    <a href="../../#.html">
                        <img class="figure-image" src="../../images/productMockup/sample-screenshot-tablet.png" alt="Ich bin eine Produktabbildung" />

                    </a>

                    <figcaption class="figure-caption"><span>Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.</span></figcaption>

                </figure>

            </div>

        </div>

    </div>

</section>

<!-- Default -->
<div class="productMockup{{#modifier}} {{.}}{{/modifier}}"{{#device}} data-device="{{.}}"{{/device}}{{#perspective}} data-view="{{.}}"{{/perspective}}>

    <div class="productMockup-figure">
        {{#figure}}
            {{render '@figure' (contextData '@productmockup' this) merge=false}}
        {{/figure}}
    </div>

</div>

<!-- Three Quarter -->
<div class="productMockup{{#modifier}} {{.}}{{/modifier}}"{{#device}} data-device="{{.}}"{{/device}}{{#perspective}} data-view="{{.}}"{{/perspective}}>

    <div class="productMockup-figure">
        {{#figure}}
            {{render '@figure' (contextData '@productmockup' this) merge=false}}
        {{/figure}}
    </div>

</div>

<!-- Tablet -->
<div class="productMockup{{#modifier}} {{.}}{{/modifier}}"{{#device}} data-device="{{.}}"{{/device}}{{#perspective}} data-view="{{.}}"{{/perspective}}>

    <div class="productMockup-figure">
        {{#figure}}
            {{render '@figure' (contextData '@productmockup' this) merge=false}}
        {{/figure}}
    </div>

</div>

<!-- Tablet Three Quarter -->
<div class="productMockup{{#modifier}} {{.}}{{/modifier}}"{{#device}} data-device="{{.}}"{{/device}}{{#perspective}} data-view="{{.}}"{{/perspective}}>

    <div class="productMockup-figure">
        {{#figure}}
            {{render '@figure' (contextData '@productmockup' this) merge=false}}
        {{/figure}}
    </div>

</div>

<!-- Documentation only -->
{{#variants}}
<section class="doc-variant">

    <header class="doc-variant-header">

            {{#title}}
        <h2 class="doc-variant-title">{{{.}}}</h2>
            {{/title}}

        <div class="doc-variant-configuration">
                {{#if modifiers}}
            <ul data-label="Modifier">
                    {{#modifiers}}
                <li>{{{.}}}</li>
                    {{/modifiers}}
            </ul>
                {{/if}}
        </div>

        <div class="doc-variant-configuration">
                {{#if themes}}
            <label>
                Theme:
                <select class="doc-select" onchange="fractal.setTheme(this, {target: '.doc-variant-samples', value: this.value});">
                        {{#themes}}
                    <option value="{{{.}}}">{{{.}}}</option>
                        {{/themes}}
                </select>
            </label>
                {{/if}}
        </div>

    </header>

    <div{{#if themes}} class="doc-variant-samples" data-theme="" style="overflow: hidden;"{{/if}}>

        {{#samples}}
            {{render '@productmockup' (contextData '@productmockup' this) merge=true}}
        {{/samples}}

    </div>

</section>
    {{/variants}}
/* Default */
{
  "figure": {
    "src": "/images/productMockup/sample-screenshot-desktop.png",
    "alt": "Ich bin eine Produktabbildung",
    "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
    "link": {
      "label": "Details anzeigen",
      "url": "#"
    }
  }
}

/* Three Quarter */
{
  "figure": {
    "src": "/images/productMockup/sample-screenshot-desktop.png",
    "alt": "Ich bin eine Produktabbildung",
    "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
    "link": {
      "label": "Details anzeigen",
      "url": "#"
    }
  },
  "perspective": "three-quarter"
}

/* Tablet */
{
  "figure": {
    "src": "/images/productMockup/sample-screenshot-tablet.png",
    "alt": "Ich bin eine Produktabbildung",
    "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
    "link": {
      "label": "Details anzeigen",
      "url": "#"
    }
  },
  "device": "tablet"
}

/* Tablet Three Quarter */
{
  "figure": {
    "src": "/images/productMockup/sample-screenshot-tablet.png",
    "alt": "Ich bin eine Produktabbildung",
    "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
    "link": {
      "label": "Details anzeigen",
      "url": "#"
    },
    "url": "#"
  },
  "device": "tablet",
  "perspective": "three-quarter"
}

/* Documentation only */
{
  "figure": {
    "src": "/images/productMockup/sample-screenshot-desktop.png",
    "alt": "Ich bin eine Produktabbildung",
    "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
    "link": {
      "label": "Details anzeigen",
      "url": "#"
    }
  },
  "variants": [
    {
      "title": "Desktop Front View (Default)",
      "themes": [
        "default",
        "accent",
        "dark"
      ],
      "samples": [
        {
          "figure": {
            "src": "/images/productMockup/sample-screenshot-desktop.png",
            "alt": "Ich bin eine Produktabbildung",
            "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
            "link": {
              "label": "Details anzeigen",
              "url": "#"
            }
          }
        }
      ]
    },
    {
      "title": "Desktop Three-quarter View",
      "themes": [
        "default",
        "accent",
        "dark"
      ],
      "samples": [
        {
          "figure": {
            "src": "/images/productMockup/sample-screenshot-desktop.png",
            "alt": "Ich bin eine Produktabbildung",
            "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
            "link": {
              "label": "Details anzeigen",
              "url": "#"
            }
          },
          "perspective": "three-quarter"
        }
      ]
    },
    {
      "title": "Tablet Front View",
      "themes": [
        "default",
        "accent",
        "dark"
      ],
      "samples": [
        {
          "figure": {
            "src": "/images/productMockup/sample-screenshot-tablet.png",
            "alt": "Ich bin eine Produktabbildung",
            "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
            "link": {
              "label": "Details anzeigen",
              "url": "#"
            }
          },
          "device": "tablet"
        }
      ]
    },
    {
      "title": "Tablet Three-quarter View",
      "themes": [
        "default",
        "accent",
        "dark"
      ],
      "samples": [
        {
          "figure": {
            "src": "/images/productMockup/sample-screenshot-tablet.png",
            "alt": "Ich bin eine Produktabbildung",
            "caption": "Selfies fixie next level trust fund jean shorts photo booth raw denim butcher mixtape ethical mustache.",
            "link": {
              "label": "Details anzeigen",
              "url": "#"
            },
            "url": "#"
          },
          "device": "tablet",
          "perspective": "three-quarter"
        }
      ]
    }
  ]
}

  • Content:
    import Perspective from "./vendor/_perspective";
    
    export default (function (){
    
        const defaults = {
            views: {
                desktop: {
                    default: {
                        backdropWidth: 900,
                        rect: [
                            [44, 10],
                            [854, 10],
                            [854, 468],
                            [44, 468]
                        ],
                    },
                    'three-quarter': {
                        backdropWidth: 900,
                        rect: [
                            [127, 66],
                            [775, 12],
                            [775, 474],
                            [127, 440]
                        ],
                    },
                },
                tablet: {
                    default: {
                        backdropWidth: 900,
                        rect: [
                            [82, 24],
                            [818, 24],
                            [818, 576],
                            [82, 576]
                        ],
                    },
                    'three-quarter': {
                        backdropWidth: 900,
                        rect: [
                            [242, 19],
                            [812, 34],
                            [700, 574],
                            [126, 487]
                        ],
                    },
                },
            },
    
        };
    
        function init() {
    
            const elements = document.querySelectorAll(".productMockup");
    
            elements.forEach((element) =>  {
                new Mockup(element);
            });
    
        }
    
        const Mockup = function(container) {
    
            const figure = container.querySelector(".productMockup-figure"),
                  sourceImage = figure.querySelector("img");
    
            if (! sourceImage) {
                console.warn("productMockup: No source image found!");
                return;
            }
    
            this.container = container;
    
            const frame = document.createElement("div");
    
            frame.classList.add('productMockup-frame');
    
            figure.appendChild(frame);
    
            const canvas = document.createElement("canvas");
    
            frame.appendChild(canvas);
    
            let view = defaults.views.desktop;
    
            if (container.dataset.device && typeof defaults.views[container.dataset.device] !== "undefined") {
                view = defaults.views[container.dataset.device];
            }
    
            if (typeof view.default !== "object") {
                console.error(`productMockup: No default view for device "${container.dataset.device}" defined!`);
                return;
            }
    
            let rect = view.default.rect;
    
            this.backdropWidth = view.default.backdropWidth;
    
            if (container.dataset.view && typeof view[container.dataset.view] !== "undefined") {
                rect = view[container.dataset.view].rect;
                this.backdropWidth = view[container.dataset.view].backdropWidth;
            }
    
            this.width = sourceImage.offsetWidth;
            this.height = sourceImage.offsetHeight;
    
            canvas.width = this.width;
            canvas.height = this.height;
    
            this.onElementInViewport(this.container, () => {
    
                const screen = new Image();
    
                screen.onload = () => {
    
                    const ratio = this.width / this.backdropWidth;
    
                    const context = canvas.getContext("2d"),
                          p = new Perspective(context, screen),
                          rectScaled = this.scaleRect(rect, ratio);
    
                    p.draw(rectScaled);
    
                    this.container.setAttribute("data-rendered", true);
    
                }
    
                this.clipLink(rect);
    
                screen.src = sourceImage.src;
    
            });
    
        }
    
        Mockup.prototype.onElementInViewport = function(element, callback) {
    
            const observer = new IntersectionObserver((entries, observer) => {
    
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        callback();
                        observer.unobserve(entry.target);
                    }
                });
    
            });
    
            observer.observe(element);
    
        }
    
        Mockup.prototype.scaleRect = function(rect, ratio) {
    
            if (typeof ratio !== "number") {
                ratio = 1;
            }
    
            let rectScaled = [];
    
            rect.forEach((values, rowIndex) => {
    
                values.forEach((value, columnIndex) => {
    
                    if (typeof rectScaled[rowIndex] !== "object") {
                        rectScaled[rowIndex] = [];
                    }
    
                    rectScaled[rowIndex][columnIndex] = value * ratio;
    
                });
    
            });
    
            return rectScaled;
    
        }
    
        Mockup.prototype.clipLink = function(rect) {
    
            const link = this.container.querySelector(".productMockup-figure .figure > a");
    
            if (! link) {
                return;
            }
    
            const ratio = this.width / this.height,
                  width = this.backdropWidth,
                  height = this.backdropWidth * 1 / ratio;
    
            let points = [];
    
            rect.forEach(point => {
    
                let [x, y] = point;
    
                x = x / width * 100;
                y = y / height * 100;
    
                points.push(`${x}% ${y}%`);
    
            });
    
            let clipPath = points.join(", ");
    
            link.style.clipPath = `polygon(${clipPath})`;
    
        }
    
        return {
            init: init
        }
    
    })();
    
  • URL: /components/raw/productmockup/_productMockup.script.js
  • Filesystem Path: components/03-fragments/productMockup/_productMockup.script.js
  • Size: 5.3 KB
  • Content:
    .productMockup {
        $aspect-ratio: 1.5;
    
        @include stack-spacing(component);
    
        width: 100%;
    
        opacity: 1;
        transition: opacity $_transition-duration--in;
    
        &:not([data-rendered="true"]) {
            opacity: 0;
        }
    
        &-figure {
            position: relative;
            z-index: 1;
    
            .figure {
                @include stack-spacing(0);
    
                position: relative;
                z-index: 1;
    
                &-image {
                    visibility: hidden;
                }
    
                &-caption {
                    @include stack-spacing(large);
                    text-align: center;
                    margin-left: auto;
                    margin-right: auto;
    
                    text-wrap: pretty;
                }
    
            }
    
        }
    
        &-frame {
            position: absolute;
            top: 0;
    
            width: 100%;
    
            &::before,
            &::after {
                content: "";
    
                display: block;
                width: 200%;
                padding-bottom: (math.div(1, $aspect-ratio) * 200%);
    
                position: absolute;
                top: 50%;
                left: 50%;
    
                transform: translate(-50%, -50%);
    
                background: transparent center center no-repeat;
                background-size: 100% auto;
    
                pointer-events: none;
            }
    
            &::before {
                z-index: -1;
                background-image: url(../images/productMockup/display-front-view--ambient.png);
            }
    
            &::after {
                z-index: 1;
                background-image: url(../images/productMockup/display-front-view--reflection.png);
            }
    
            canvas {
                display: block;
                width: 100%;
                aspect-ratio: #{$aspect-ratio};
    
                background: url(../images/productMockup/display-front-view.png) center center no-repeat;
                background-size: 200% auto;
    
                position: relative;
            }
    
        }
    
        &-link {
            display: block;
    
            position: absolute;
            inset: 0;
            z-index: 2;
        }
    
        &[data-device="three-quarter"] &-figure,
        &[data-view="three-quarter"] &-figure {
    
            .figure-caption {
                @include stack-spacing();
            }
    
        }
    
        &[data-view="three-quarter"] &-frame {
    
            &::before {
                background-image: url(../images/productMockup/display-three-quarter-view--ambient.png);
            }
    
            &::after {
                background-image: url(../images/productMockup/display-three-quarter-view--reflection.png);
            }
    
            canvas {
                background-image: url(../images/productMockup/display-three-quarter-view.png);
            }
    
        }
    
        &[data-device="tablet"] &-frame {
    
            &::before {
                background-image: url(../images/productMockup/tablet-front-view--ambient.png);
            }
    
            &::after {
                background-image: url(../images/productMockup/tablet-front-view--reflection.png);
            }
    
            canvas {
                background-image: url(../images/productMockup/tablet-front-view.png);
            }
    
        }
    
        &[data-device="tablet"][data-view="three-quarter"] &-frame {
    
            &::before {
                background-image: url(../images/productMockup/tablet-three-quarter-view--ambient.png);
            }
    
            &::after {
                background-image: url(../images/productMockup/tablet-three-quarter-view--reflection.png);
            }
    
            canvas {
                background-image: url(../images/productMockup/tablet-three-quarter-view.png);
            }
    
        }
    
    }
    
  • URL: /components/raw/productmockup/_productMockup.styles.scss
  • Filesystem Path: components/03-fragments/productMockup/_productMockup.styles.scss
  • Size: 3.4 KB