diff options
-rw-r--r-- | src/dumbymap.mjs | 31 | ||||
-rw-r--r-- | src/utils.mjs | 56 |
2 files changed, 67 insertions, 20 deletions
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 9a38e24..6e4c00e 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
@@ -6,10 +6,8 @@ import MarkdownItTocDoneRight from 'markdown-it-toc-done-right' | |||
6 | import LeaderLine from 'leader-line' | 6 | import LeaderLine from 'leader-line' |
7 | import PlainDraggable from 'plain-draggable' | 7 | import PlainDraggable from 'plain-draggable' |
8 | import { renderWith, parseConfigsFromYaml } from 'mapclay' | 8 | import { renderWith, parseConfigsFromYaml } from 'mapclay' |
9 | import { onRemove, animateRectTransition } from './utils' | 9 | import { onRemove, animateRectTransition, throttle } from './utils' |
10 | 10 | ||
11 | // Utils {{{ | ||
12 | // }}} | ||
13 | // FUNCTION: Get DocLinks from special anchor element {{{ | 11 | // FUNCTION: Get DocLinks from special anchor element {{{ |
14 | 12 | ||
15 | const docLinkSelector = 'a[href^="#"][title^="=>"]' | 13 | const docLinkSelector = 'a[href^="#"][title^="=>"]' |
@@ -229,6 +227,9 @@ export const generateMaps = async (container, callback) => { | |||
229 | showcase.classList.add('Showcase') | 227 | showcase.classList.add('Showcase') |
230 | 228 | ||
231 | // Focus Map {{{ | 229 | // Focus Map {{{ |
230 | const toShowcaseWithThrottle = throttle(animateRectTransition, 300) | ||
231 | const fromShowCaseWithThrottle = throttle(animateRectTransition, 300) | ||
232 | |||
232 | const mapFocusObserver = () => new MutationObserver((mutations) => { | 233 | const mapFocusObserver = () => new MutationObserver((mutations) => { |
233 | const mutation = mutations.at(-1) | 234 | const mutation = mutations.at(-1) |
234 | const target = mutation.target | 235 | const target = mutation.target |
@@ -238,31 +239,41 @@ export const generateMaps = async (container, callback) => { | |||
238 | if (shouldBeInShowcase) { | 239 | if (shouldBeInShowcase) { |
239 | if (showcase.contains(target)) return | 240 | if (showcase.contains(target)) return |
240 | 241 | ||
241 | // Placeholder for map in Showcase, it should has the same rect | 242 | // Placeholder for map in Showcase, it should has the same DOMRect |
242 | const placeholder = target.cloneNode(true) | 243 | const placeholder = target.cloneNode(true) |
243 | placeholder.classList.remove('map-container') | 244 | placeholder.classList.remove('map-container') |
244 | placeholder.setAttribute('data-placeholder', target.id) | 245 | placeholder.setAttribute('data-placeholder', target.id) |
245 | target.parentElement.replaceChild(placeholder, target) | 246 | target.parentElement.replaceChild(placeholder, target) |
246 | 247 | ||
247 | // To fit showcase, remove all inline style | 248 | // To fit showcase, remove all inline style |
248 | target.style = "" | 249 | target.removeAttribute('style') |
249 | showcase.appendChild(target) | 250 | showcase.appendChild(target) |
250 | 251 | ||
251 | // Resume rect from Semantic HTML to Showcase, with animation | 252 | // Resume rect from Semantic HTML to Showcase, with animation |
252 | animateRectTransition(target, placeholder.getBoundingClientRect(), true) | 253 | toShowcaseWithThrottle(target, placeholder.getBoundingClientRect(), { |
254 | duration: 300, | ||
255 | resume: true | ||
256 | }) | ||
253 | } else if (showcase.contains(target)) { | 257 | } else if (showcase.contains(target)) { |
254 | const placeholder = htmlHolder.querySelector(`[data-placeholder="${target.id}"]`) | 258 | const placeholder = htmlHolder.querySelector(`[data-placeholder="${target.id}"]`) |
255 | if (!placeholder) throw Error(`Cannot fine placeholder for map "${target.id}"`) | 259 | if (!placeholder) throw Error(`Cannot fine placeholder for map "${target.id}"`) |
260 | const animation = fromShowCaseWithThrottle(target, placeholder.getBoundingClientRect(), { | ||
261 | duration: 300 | ||
262 | }) | ||
256 | 263 | ||
257 | const afterAnimation = () => { | 264 | const afterAnimation = () => { |
258 | placeholder.parentElement.replaceChild(target, placeholder) | 265 | placeholder.parentElement.replaceChild(target, placeholder) |
259 | target.style = placeholder.style.cssText | 266 | target.style = placeholder.style.cssText |
260 | placeholder.remove() | 267 | placeholder.remove() |
261 | } | 268 | } |
262 | animateRectTransition(target, placeholder.getBoundingClientRect()) | 269 | |
263 | .finished | 270 | if (animation) { |
264 | .then(afterAnimation) | 271 | animation.finished |
265 | .catch(afterAnimation) | 272 | .then(afterAnimation) |
273 | .catch(afterAnimation) | ||
274 | } else { | ||
275 | afterAnimation() | ||
276 | } | ||
266 | } | 277 | } |
267 | }) | 278 | }) |
268 | // }}} | 279 | // }}} |
diff --git a/src/utils.mjs b/src/utils.mjs index 4c58dc1..e694d4a 100644 --- a/src/utils.mjs +++ b/src/utils.mjs | |||
@@ -1,4 +1,9 @@ | |||
1 | // Disconnect MutationObserver if element is removed | 1 | /** |
2 | * Do callback when HTMLElement in removed | ||
3 | * | ||
4 | * @param {HTMLElement} element observing | ||
5 | * @param {Function} callback | ||
6 | */ | ||
2 | export const onRemove = (element, callback) => { | 7 | export const onRemove = (element, callback) => { |
3 | const parent = element.parentNode; | 8 | const parent = element.parentNode; |
4 | if (!parent) throw new Error("The node must already be attached"); | 9 | if (!parent) throw new Error("The node must already be attached"); |
@@ -16,10 +21,21 @@ export const onRemove = (element, callback) => { | |||
16 | obs.observe(parent, { childList: true, }); | 21 | obs.observe(parent, { childList: true, }); |
17 | } | 22 | } |
18 | 23 | ||
19 | // Animation for new rectangle | 24 | /** |
20 | export const animateRectTransition = (child, rect, resume = false) => { | 25 | * Animate transition of DOMRect, with Web Animation API |
26 | * | ||
27 | * @param {HTMLElement} element Element which animation applies | ||
28 | * @param {DOMRect} rect DOMRect for transition | ||
29 | * @param {Object} options | ||
30 | * @param {Boolean} options.resume If true, transition starts from rect to DOMRect of element | ||
31 | * @param {Number} options.duration Duration of animation in milliseconds | ||
32 | * @returns {Animation} https://developer.mozilla.org/en-US/docs/Web/API/Animation | ||
33 | */ | ||
34 | export const animateRectTransition = (element, rect, options = {}) => { | ||
35 | if (!element.parentElement) throw new Error("The node must already be attached"); | ||
36 | |||
21 | const { width: w1, height: h1, left: x1, top: y1 } = rect | 37 | const { width: w1, height: h1, left: x1, top: y1 } = rect |
22 | const { width: w2, height: h2, left: x2, top: y2 } = child.getBoundingClientRect() | 38 | const { width: w2, height: h2, left: x2, top: y2 } = element.getBoundingClientRect() |
23 | 39 | ||
24 | const rw = w1 / w2 | 40 | const rw = w1 / w2 |
25 | const rh = h1 / h2 | 41 | const rh = h1 / h2 |
@@ -36,13 +52,33 @@ export const animateRectTransition = (child, rect, resume = false) => { | |||
36 | { transform: transform1, opacity: 1 }, | 52 | { transform: transform1, opacity: 1 }, |
37 | { transform: transform2, opacity: 0.3 }, | 53 | { transform: transform2, opacity: 0.3 }, |
38 | ] | 54 | ] |
55 | if (options.resume === true) keyframes.reverse() | ||
39 | 56 | ||
40 | return child.animate( | 57 | return element.animate( |
41 | resume | 58 | keyframes, |
42 | ? keyframes.reverse() | ||
43 | : keyframes, | ||
44 | { | 59 | { |
45 | duration: 300, | 60 | duration: options.duration ?? 300, |
46 | easing: 'ease-in-out', | 61 | easing: 'ease-in-out', |
47 | }); | 62 | } |
63 | ); | ||
64 | } | ||
65 | |||
66 | |||
67 | /** | ||
68 | * Throttle for function call | ||
69 | * | ||
70 | * @param {Function} func | ||
71 | * @param {Number} delay milliseconds | ||
72 | * @returns {Any} return value of function call, or null if throttled | ||
73 | */ | ||
74 | export function throttle(func, delay) { | ||
75 | let timerFlag = null; | ||
76 | |||
77 | return (...args) => { | ||
78 | if (timerFlag !== null) return null | ||
79 | timerFlag = setTimeout(() => { | ||
80 | timerFlag = null; | ||
81 | }, delay); | ||
82 | return func(...args); | ||
83 | }; | ||
48 | } | 84 | } |