aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/dumbymap.mjs31
-rw-r--r--src/utils.mjs56
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'
6import LeaderLine from 'leader-line' 6import LeaderLine from 'leader-line'
7import PlainDraggable from 'plain-draggable' 7import PlainDraggable from 'plain-draggable'
8import { renderWith, parseConfigsFromYaml } from 'mapclay' 8import { renderWith, parseConfigsFromYaml } from 'mapclay'
9import { onRemove, animateRectTransition } from './utils' 9import { 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
15const docLinkSelector = 'a[href^="#"][title^="=>"]' 13const 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 */
2export const onRemove = (element, callback) => { 7export 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/**
20export 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 */
34export 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 */
74export 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}