From 82a88c5b8151a22b8bd09641ccde2687ee250a54 Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Thu, 11 May 2023 05:27:06 +0100 Subject: [PATCH] Handle scrollable elements for highlighting --- index.html | 89 ++++++++++++++++++++++++++++++++++++++++++++++-- src/config.ts | 2 ++ src/highlight.ts | 28 ++------------- src/math.ts | 11 ------ src/stage.ts | 2 +- src/utils.ts | 52 ++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 39 deletions(-) delete mode 100644 src/math.ts create mode 100644 src/utils.ts diff --git a/index.html b/index.html index d737ad7..d87ec96 100644 --- a/index.html +++ b/index.html @@ -94,6 +94,15 @@ padding: 10px; line-height: 1.75; } + + #scrollable-area { + height: 300px; + overflow: auto; + border: 1px solid #ccc; + padding: 10px; + border-radius: 5px; + margin: 50px 0; + } @@ -110,6 +119,8 @@ + + @@ -131,14 +142,14 @@ off the Lights" widgets that you might have seen on video players online, etc.

-

+

Driver.js is written in Vanilla JS, has zero dependencies and is highly customizable. It has several options allowing you to manipulate how it behaves and also provides you the hooks to manipulate the elements as they are highlighted, about to be highlighted, or deselected.

-

Installation

+

Installation

You can install it using yarn or npm, whatever you prefer.

@@ -175,6 +186,62 @@ npm install driver.js
+
+

+ First -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

+ Second -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

+ Third -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

+ Fourth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

+ Fifth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

+ Sixth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

+ Seventh -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

+ Eighth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

+ Ninth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Consequuntur dicta ipsum labore quod tempora ullam? Alias consequatur + doloremque laborum maxime necessitatibus nostrum odio, officiis + quibusdam veniam! Doloribus eos id quaerat. +

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in inventore ipsa iure laudantium @@ -249,6 +316,24 @@ npm install driver.js { driverObj.highlight({ element: ".buttons" }); }, 1000); + + window.setTimeout(() => { + driverObj.highlight({ element: "#installation-head" }); + }, 1500); + }); + + document + .getElementById("scrollable-area-btn") + .addEventListener("click", () => { + const driverObj = driver({ animate: true }); + driverObj.highlight({ element: "#scrollable-area" }); + }); + + document + .getElementById("inner-scroll-area-btn") + .addEventListener("click", () => { + const driverObj = driver({ animate: true }); + driverObj.highlight({ element: "#third-scroll-paragraph" }); }); document diff --git a/src/config.ts b/src/config.ts index b116566..da53834 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ export type Config = { animate?: boolean; + smoothScroll?: boolean; allowClose?: boolean; opacity?: number; }; @@ -11,6 +12,7 @@ export function configure(config: Config = {}) { animate: true, allowClose: true, opacity: 0.7, + smoothScroll: true, ...config, }; } diff --git a/src/highlight.ts b/src/highlight.ts index 100f4ed..1c997fa 100644 --- a/src/highlight.ts +++ b/src/highlight.ts @@ -2,6 +2,7 @@ import { DriveStep } from "./driver"; import { refreshStage, trackActiveElement, transitionStage } from "./stage"; import { getConfig } from "./config"; import { refreshPopover, renderPopover } from "./popover"; +import { bringInView } from "./utils"; let previousHighlight: Element | undefined; let activeHighlight: Element | undefined; @@ -48,7 +49,6 @@ function transferHighlight(from: Element, to: Element) { if (getConfig("animate") && elapsed < duration) { transitionStage(elapsed, duration, from, to); } else { - bringInView(to); trackActiveElement(to); renderPopover(to); @@ -63,34 +63,12 @@ function transferHighlight(from: Element, to: Element) { currentTransitionCallback = animate; window.requestAnimationFrame(animate); + bringInView(to); + from.classList.remove("driver-active-element"); to.classList.add("driver-active-element"); } -function bringInView(element: Element) { - if (!element || isElementInView(element)) { - return; - } - - element.scrollIntoView({ - behavior: "smooth", - inline: "center", - block: "center", - }); -} - -function isElementInView(element: Element) { - const rect = element.getBoundingClientRect(); - - return ( - rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= - (window.innerHeight || document.documentElement.clientHeight) && - rect.right <= (window.innerWidth || document.documentElement.clientWidth) - ); -} - export function destroyHighlight() { activeHighlight = undefined; currentTransitionCallback = undefined; diff --git a/src/math.ts b/src/math.ts deleted file mode 100644 index 18b0b76..0000000 --- a/src/math.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function easeInOutQuad( - elapsed: number, - initialValue: number, - amountOfChange: number, - duration: number -): number { - if ((elapsed /= duration / 2) < 1) { - return (amountOfChange / 2) * elapsed * elapsed + initialValue; - } - return (-amountOfChange / 2) * (--elapsed * (elapsed - 2) - 1) + initialValue; -} diff --git a/src/stage.ts b/src/stage.ts index 87796fa..bc967d8 100644 --- a/src/stage.ts +++ b/src/stage.ts @@ -1,4 +1,4 @@ -import { easeInOutQuad } from "./math"; +import { easeInOutQuad } from "./utils"; import { onDriverClick } from "./events"; import { trigger } from "./hooks"; import { getConfig } from "./config"; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..46b6231 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,52 @@ +import { getConfig } from "./config"; + +export function easeInOutQuad( + elapsed: number, + initialValue: number, + amountOfChange: number, + duration: number +): number { + if ((elapsed /= duration / 2) < 1) { + return (amountOfChange / 2) * elapsed * elapsed + initialValue; + } + return (-amountOfChange / 2) * (--elapsed * (elapsed - 2) - 1) + initialValue; +} + +export function bringInView(element: Element) { + if (!element || isElementInView(element)) { + return; + } + + const shouldSmoothScroll = getConfig("smoothScroll"); + + element.scrollIntoView({ + // Removing the smooth scrolling for elements which exist inside the scrollable parent + // This was causing the highlight to not properly render + behavior: + !shouldSmoothScroll || hasScrollableParent(element) ? "auto" : "smooth", + inline: "center", + block: "center", + }); +} + +function hasScrollableParent(e: Element) { + if (!e || !e.parentElement) { + return; + } + + const parent = e.parentElement as HTMLElement & { scrollTopMax?: number }; + + return parent.scrollHeight > parent.clientHeight; +} + +function isElementInView(element: Element) { + const rect = element.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= + (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); +}