How to create a custom annotation that is animated?

Q:

Is it possible to create a custom annotation that is animated when viewed in WebViewer? I only need to have it display correctly in WebViewer, it doesn’t need to work in other PDF viewers.

A:

To create HTML elements that you can control the styling of and animate or add JavaScript event handlers to, one approach is to create an HTML annotation and then assign it to a field with a particular name. Then extend the createInnerElement function to apply custom handling for widgets with that name.

In this example it creates a text widget annotation, and note that you will need to use a custom CSS file specified with the css constructor option as shown here PDFTron Systems Inc. | Documentation

CSS file:

.animated-element {
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0% {
    background-color: #001F3F;
  }
  100% {
    background-color: #FF4136;
  }
}

JavaScript

const { documentViewer, annotationManager, Annotations } = instance.Core;
const fieldManager = annotationManager.getFieldManager();

const customFieldName = 'custom-annotation-field';

const createInnerElement = Annotations.TextWidgetAnnotation.prototype.createInnerElement;
Annotations.TextWidgetAnnotation.prototype.createInnerElement = function () {
  if (this.fieldName !== customFieldName) {
    // perform the default behavior for normal text widgets
    return createInnerElement.apply(this, arguments);
  }

  // create custom element and set event handlers on custom element
  const element = document.createElement('div');

  const animatedClass = 'animated-element';

  element.addEventListener('mouseenter', () => {
    element.classList.add(animatedClass);
  });

  element.addEventListener('mouseleave', () => {
    element.classList.remove(animatedClass);
  });

  // optional if you need to clean up anything when removed from the DOM
  // in this case don't want to be stuck in the mouse enter state
  onRemove(element, () => {
    element.classList.remove(animatedClass);
  });

  return element;
};

function onRemove(element, onDetachCallback) {
  const observer = new MutationObserver(function () {
    function isDetached(el) {
      if (el.parentNode === document) {
        return false;
      } else if (el.parentNode === null) {
        return true;
      } else {
        return isDetached(el.parentNode);
      }
    }

    if (isDetached(element)) {
      onDetachCallback();
    }
  })

  observer.observe(instance.UI.iframeWindow.document, {
    childList: true,
    subtree: true
  });
};

Annotations.WidgetAnnotation.getCustomStyles = widget => {
  if (widget.fieldName === customFieldName) {
    // optionally set initial CSS styles
    return {
      backgroundColor: '#001F3F'
    };
  }
};

const customField = new Annotations.Forms.Field(customFieldName, {
  type: 'Tx',
  value: '',
});

documentViewer.addEventListener('documentLoaded', () => {
  fieldManager.addField(customField);

  createCustomAnnotation({ x: 100, y: 100, width: 100, height: 20, pageNumber: 1 });
  createCustomAnnotation({ x: 100, y: 200, width: 100, height: 20, pageNumber: 1 })
});

const createCustomAnnotation = ({ x, y, width, height, pageNumber }) => {
  // create a new custom annotation with the specified attributes
  const annot = new Annotations.TextWidgetAnnotation(customField);
  annot.X = x;
  annot.Y = y;
  annot.Width = width;
  annot.Height = height;
  annot.PageNumber = pageNumber;

  annotationManager.addAnnotation(annot);
  annotationManager.drawAnnotations({
    pageNumber,
    majorRedraw: true
  });
};

1 Like