xHighlight Techniques: Technical illustration & Scroll navigation after deployment

This post covers two different topics that can be used with xHighlight (XPR role) at different stages of its implementation.

 

The first part focuses on creating interactive elements with technical illustrations features directly in xHighlight (SVG, Callouts, Part List, ...) and after publish.

The second part shows how to integrate and control scene navigation through mouse scrolling after publishing and implementing your experience media on a website.

 

LIVE EXAMPLE:  

https://demo.solution-server.com/leaders2025/xH/eFoil/

 

Part 1 : TECHNICAL ILLUSTRATION + INTERACTIVITY

This part focuses on creating interactive technical illustrations directly in xHighlight.

It covers how to prepare and publish the illustration, including SVG-based workflows, while making individual parts interactive.

Topics covered:

  • Technical illustration: Create and prepare a technical illustration directly in xHighlight.
  • Part list & Callouts: Add callouts to highlight and provide information about specific parts in a list of the different parts included in the illustration.
  • SVG Publication: Publish the experience in SVG format to make individual parts interactive to enhance the render.

Video : 

 

Part 2 : SCROLL NAVIGATION

The second part focuses on the web integration of a completed xHighlight experience.

Once the experience has been fully authored and published, the provided JavaScript code can be used to connect the webpage's mouse scroll behavior to the different scenes of the experience.

Before implementing the scroll interaction, the experience needs to be correctly prepared in xHighlight. The scenes should be properly structured, with their transitions and timing adjusted to ensure smooth progression when moving from one scene to another.

Live example of scroll navigation with implemented code : 

 

 

CODE EXAMPLE:

Once the Experience Media has been initialised, the following steps show you how to retrieve the available scenes and connect them to the webpage scroll.

 

BEFORE THE WORKFLOW : REQUIRED SETUP 🔧 

What it does: Sets up the variables and scroll container required to connect the webpage scroll to the xHighlight scenes.

// Store the scenes retrieved from the xHighlight experience
let globalScenes = [];

// Keep track of the currently displayed scene
let lastScrollIndex = -1;

// Prevent multiple scene changes from being triggered simultaneously
let isScrollLocked = false;
let pendingIndex = null;

// Store the scroll container and its total scrollable distance
let expWrapper, expTotal;

// Delay scene changes slightly while scrolling
const SCENE_NAV_DEBOUNCE = 90;

// Store the scroll/navigation state
let isJumping = false;
let jumpRaf = null;
let sceneNavTimer = null;

// Define the scroll container
expWrapper = document.getElementById('experience');

// Calculate the available scroll distance
function recalcMetrics() {
  expTotal = expWrapper.offsetHeight - window.innerHeight;
}

recalcMetrics();
window.addEventListener('resize', recalcMetrics);

 

This workflow connects the webpage scroll position to the corresponding scenes of a published xHighlight experience.

🗒️  Note : The parameters in the code can be adapted depending on the structure of the experience and the desired scrolling behavior for your website.

 

 

SCENES 🎥 

What it does: Defines the order in which the scenes will be displayed as the user scrolls through the webpage.

// Define the scenes in the order they should be reached while scrolling
const SCROLL_SCENES = [
  { name: "Scene 1" },
  { name: "Scene 2" },
  { name: "Scene 3" },
  { name: "Scene 4" },
  { name: "Scene 5" },
  {...}
];

// Create enough scroll space for all scenes
expWrapper.style.height = `calc(100vh * \\\${SCROLL_SCENES.length})`;

// Recalculate the available scroll distance
recalcMetrics();

 

xHighlight PLAYER ⏯️ 

What it does: Retrieves the xHighlight experience player so its APIs can be called from the webpage.

function P() {
 return document.getElementById("ExperiencePlayer");
}

 

RETRIEVE SCENES 📣 

What it does: Waits for the xHighlight experience to be ready, then retrieves its available scenes.

window.addEventListener('message', function(e) {
  if (!e?.data || e.data.protocol !== "ExperienceCommunication") return;

  if (e.data.action === "ExperienceReady") {
    P()?.xHgetScenes().then(scenes => {
      globalScenes = scenes;
      evaluateScroll(true);
    });
  }
});

 

SCROLL PROGRESS 📏 

What it does: Calculates the user's scroll progress and converts it into a corresponding scene index.

function indexFromRect(rect) {
  const scrollProgress = Math.min(
    Math.max(-rect.top / expTotal, 0),
    1
  );
  return Math.min(
    Math.floor(scrollProgress * SCROLL_SCENES.length),
    SCROLL_SCENES.length - 1
  );
}

 

SCENE SELECTION 🔎 

What it does: Finds the scene corresponding to the current scroll position and triggers it through the xHighlight API, while preventing overlapping scene changes.

function goToScrollIndex(index) {
  if (!globalScenes.length || index === lastScrollIndex) return;
  // Prevent multiple scene changes from being triggered simultaneously
  if (isScrollLocked) {
    pendingIndex = index;
    return;
  }
  // Find the corresponding xHighlight scene
  const scene = globalScenes.find(
    s => s.name === SCROLL_SCENES[index].name
  );
  if (!scene) return;
  isScrollLocked = true;
  lastScrollIndex = index;
  // Play the selected scene
  P()?.xHplayScene(scene.id).then(() => {
    P()?.xHpause();
    isScrollLocked = false;
    // Apply the latest requested scene once the current scene is ready
    if (pendingIndex !== null) {
      const next = pendingIndex;
      pendingIndex = null;
      if (next !== lastScrollIndex) {
        goToScrollIndex(next);
      }
    }
  });
}

 

SCROLL EVENT ↕️ 

What it does: Detects the user's scroll and updates the xHighlight experience accordingly.

let scrollTicking = false;
function evaluateScroll(immediate) {
  const rect = expWrapper.getBoundingClientRect();
  if (isJumping && !immediate) return;
  if (immediate) {
    goToScrollIndex(indexFromRect(rect));
    return;
  }
  clearTimeout(sceneNavTimer);
  sceneNavTimer = setTimeout(() => {
    goToScrollIndex(
      indexFromRect(expWrapper.getBoundingClientRect())
    );
  }, SCENE_NAV_DEBOUNCE);
}
window.addEventListener('scroll', () => {
  if (scrollTicking) return;
  scrollTicking = true;
  requestAnimationFrame(() => {
    evaluateScroll();
    scrollTicking = false;
  });
}, { passive: true });

 

OPTIONAL : CLICK TO NAVIGATE 🖱️

What it does: Allows users to click on a scene title to directly navigate to the corresponding xHighlight scene and smoothly move the webpage to its associated scroll position.

function jumpToScene(index) {
  if (!globalScenes.length) return;
  isJumping = true;
  cancelAnimationFrame(jumpRaf);
  clearTimeout(sceneNavTimer);
  goToScrollIndex(index);
  const ratio =
    SCROLL_SCENES.length > 1
      ? index / (SCROLL_SCENES.length - 1)
      : 0;
  const targetTop =
    expWrapper.offsetTop + ratio * expTotal;
  window.scrollTo({
    top: targetTop,
    behavior: 'smooth'
  });
  // Wait until the webpage reaches the target position before resuming normal scroll-based navigation
  let lastY = window.scrollY;
  let stableFrames = 0;
  const maxWait = Date.now() + 3000;
  function checkSettled() {
    const y = window.scrollY;
    const closeEnough = Math.abs(y - targetTop) < 2;
    const notMoving = Math.abs(y - lastY) < 1;
    lastY = y;
    stableFrames = notMoving ? stableFrames + 1 : 0;
    if (
      (closeEnough && stableFrames >= 3) ||
      Date.now() > maxWait
    ) {
      isJumping = false;
      evaluateScroll(true);
      return;
    }
    jumpRaf = requestAnimationFrame(checkSettled);
  }
  jumpRaf = requestAnimationFrame(checkSettled);
}
sceneTitle.onclick = () => jumpToScene(index);

 

ℹ️  If you have any question or issue feel free to ask. You can check and inspect the demo website. You can even use it to be inspired for your website and the implementation of your Experience Media.