Building an Overlay Image Widget

There’s many use cases for overlaying an image with another one: magnifying an area, showing a person’s avatar, or highlighting some detail. Your imagination is the limit.

In this brief tutorial, we are going to show you how to have an image widget render an additional image on top of the actual one. The additional image’s width, border offset, and border radius will be specifiable, allowing you to position the overlay flexibly within the bounds of the image underneath. To achieve this, we are going to use a couple of Bootstrap classes in conjunction with quite a few inline styles to not have to define a multitude of additional CSS classes, which would have blown up this tutorial.

Getting started

Once more, the Scrivito Example App serves us as a starting point. Widgets are represented as directories in the “src/Widgets” directory, so just create one named “OverlayImageWidget” and add the files for the class definition, the editing configuration, and the component to it. We’re providing the contents of these files below and will explain how they work.

Provide the widget class

A widget’s class declares the attributes it uses. In our case, we need two reference attributes for the images, image and overlayImage, plus one each for the various configurable overlay styles, e.g. alignment:

src/Widgets/OverlayImageWidget/OverlayImageWidgetClass.js
Copy
import * as Scrivito from "scrivito";

const OverlayImageWidget = Scrivito.provideWidgetClass("OverlayImageWidget", {
  attributes: {
    image: "reference",
    overlayImage: "reference",
    alignment: ["enum", { values: ["topLeft", "topRight", "bottomLeft", "bottomRight"] }],
    width: ["enum", { values: ["15", "25", "35", "45"] }],
    rounded: ["enum", { values: ["5", "10", "15", "50"] }],
    offset: ["enum", { values: ["5", "10", "15", "20"] }],
  },
});

export default OverlayImageWidget;

Note that all numerical values are given in percent, not in pixels, as we want the rendered result to be responsive.

Provide the editing configuration

As you might have gathered from other tutorials or the SDK's API documentation, Scrivito comes with helper React components such as Scrivito.ImageTag for rendering attributes and making them editable in place. In other words: Only attributes that cannot be changed directly on a page need to be configured for editing them via the properties dialog. In the case of our “OverlayImageWidget”, this applies to the style attributes.

src/Widgets/OverlayImageWidget/OverlayImageWidgetEditingConfig.js
Copy
import * as Scrivito from "scrivito";
import imageWidgetIcon from "../../assets/images/image_widget.svg";

Scrivito.provideEditingConfig("OverlayImageWidget", {
  title: "Overlay Image",
  thumbnail: imageWidgetIcon,
  attributes: {
    alignment: {
      title: "Overlay alignment",
      description: "Default: Top right",
      values: [
        { value: "topLeft", title: "Top left" },
        { value: "topRight", title: "Top right" },
        { value: "bottomLeft", title: "Bottom left" },
        { value: "bottomRight", title: "Bottom right" },
      ],
    },
    width: {
      title: "Overlay width",
      description: "Default: 25 %",
      values: [
        { value: "15", title: "15 %" },
        { value: "25", title: "25 %" },
        { value: "35", title: "35 %" },
        { value: "45", title: "45 %" },
      ],
    },
    borderRadius: {
      title: "Overlay border radius",
      description: "Default: 0",
      values: [
        { value: "5", title: "5 %" },
        { value: "10", title: "10 %" },
        { value: "15", title: "15 %" },
        { value: "50", title: "50 %" },
      ],
    },
    offset: {
      title: "Overlay border offset",
      description: "Default: off",
      values: [
        { value: "5", title: "5 %" },
        { value: "10", title: "10 %" },
        { value: "15", title: "15 %" },
        { value: "20", title: "20 %" },
      ],
    },
  },
  properties: ["alignment", "width", "borderRadius", "offset"],
  initialContent: {
    alignment: "topRight",
    width: "25",
  },
});

All of the attributes the above configuration makes editable are of the enum type, meaning that zero or exactly one value can be selected from a predefined set. We’ve already defined the values of each attribute in the widget class definition; the editing configuration maps those values to strings (title) to be displayed for them on the properties dialog. Additionally, two attributes (alignment and width) are given initial values, which will be set for every new “OverlayImageWidget” placed onto a page.

Provide the rendering component

A widget component’s task is to render widget instances based on their respective attributes. Our “OverlayImageWidget” component renders its two images as child elements of a <div> container with relative positioning. The overlay is positioned absolutely, meaning that its dimensions etc. refer to the container.

src/Widgets/OverlayImageWidget/OverlayImageWidgetComponent.js
Copy
import * as React from "react";
import * as Scrivito from "scrivito";

Scrivito.provideComponent("OverlayImageWidget", ({ widget }) => {
  const alignment = widget.get("alignment") || "topRight";
  const width = parseInt(widget.get("width") || "25");
  const offset = parseInt(widget.get("offset") || "0");
  const offsetPercent = `${offset}%`;
  const corners = {
    topRight: {top: offsetPercent, right: offsetPercent},
    topLeft: {top: offsetPercent, left: offsetPercent},
    bottomRight: {bottom: offsetPercent, right: offsetPercent},
    bottomLeft: {bottom: offsetPercent, left: offsetPercent}
  }
  const style = corners[alignment];
  style.maxHeight = `${100 - offset}%`;
  style.maxWidth = `${width}%`;
  style.borderRadius = `${widget.get("borderRadius") || "0"}%`;
  style.borderWidth = `${0.1 * width - 0.5}px`;
  style.borderColor = 'white';
  style.borderStyle = 'solid';

  return (
    <div className="position-relative mb-3">
      <Scrivito.ImageTag
        content={widget}
        attribute="image"
        className="img-fluid mb-0"
      />
      <Scrivito.ImageTag
        content={widget}
        attribute="overlayImage"
        className="position-absolute mb-0"
        style={style}
      />
    </div>
  );
});

As you can see, the component first fetches the overlay’s styles, and initializes the style tag attribute passed to the overlay image further down. After assigning the various other parameters to style, the <div> container with its two child images is rendered using Scrivito.ImageTag.

Congratulations!

You now have a widget for displaying an image that is overlaid by another one which is adjustable in size, shape, and position. To make this widget even more versatile, you could:

  • Provide finer grained percentages.
  • Make the overlay’s border color, width, and style configurable as well.
  • Provide separate horizontal and vertical offsets.

Have fun!