diff options
author | Hsieh Chin Fan <pham@topo.tw> | 2024-09-22 17:52:48 +0800 |
---|---|---|
committer | Hsieh Chin Fan <pham@topo.tw> | 2024-09-22 17:58:09 +0800 |
commit | 0a1d70dd6017164e13386997b7b03eab47045e0a (patch) | |
tree | 289214c209ca8a399139b75f70934485079acd89 | |
parent | 516aab9de3d6ad5b52da75666ce9f6531c913be6 (diff) |
feat: animation for creating draggable block
* rename class name: draggable -> draggable-part
* code refactor: separate logic about working with plain-draggable
* get original rect before creating blocks
-rw-r--r-- | src/Layout.mjs | 172 | ||||
-rw-r--r-- | src/css/dumbymap.css | 6 |
2 files changed, 100 insertions, 78 deletions
diff --git a/src/Layout.mjs b/src/Layout.mjs index 2deef01..8a9f316 100644 --- a/src/Layout.mjs +++ b/src/Layout.mjs | |||
@@ -1,5 +1,5 @@ | |||
1 | import PlainDraggable from 'plain-draggable' | 1 | import PlainDraggable from 'plain-draggable' |
2 | import { onRemove } from './utils' | 2 | import { onRemove, animateRectTransition } from './utils' |
3 | 3 | ||
4 | export class Layout { | 4 | export class Layout { |
5 | constructor(options = {}) { | 5 | constructor(options = {}) { |
@@ -32,6 +32,7 @@ export class SideBySide extends Layout { | |||
32 | containment: { left: '25%', top: 0, right: '75%', height: 0 }, | 32 | containment: { left: '25%', top: 0, right: '75%', height: 0 }, |
33 | }) | 33 | }) |
34 | draggable.draggableCursor = "grab" | 34 | draggable.draggableCursor = "grab" |
35 | |||
35 | draggable.onDrag = (pos) => { | 36 | draggable.onDrag = (pos) => { |
36 | handle.style.transform = 'unset' | 37 | handle.style.transform = 'unset' |
37 | resizeByLeft(pos.left) | 38 | resizeByLeft(pos.left) |
@@ -51,16 +52,82 @@ export class SideBySide extends Layout { | |||
51 | export class Overlay extends Layout { | 52 | export class Overlay extends Layout { |
52 | name = "overlay" | 53 | name = "overlay" |
53 | 54 | ||
54 | enterHandler = (dumbymap) => { | 55 | saveLeftTopAsData = (element) => { |
55 | const container = dumbymap.htmlHolder | 56 | const { left, top } = element.getBoundingClientRect() |
56 | const moveIntoDraggable = (block) => { | 57 | element.setAttribute('data-left', left) |
58 | element.setAttribute('data-top', top) | ||
59 | } | ||
60 | |||
61 | addDraggable = (element) => { | ||
62 | // Add draggable part | ||
63 | const draggablePart = document.createElement('div') | ||
64 | element.appendChild(draggablePart) | ||
65 | draggablePart.className = 'draggable-part' | ||
66 | draggablePart.innerHTML = '<div class="handle">\u2630</div>' | ||
67 | draggablePart.title = 'Use middle-click to remove block' | ||
68 | draggablePart.onmouseup = (e) => { | ||
69 | // Hide block with middle click | ||
70 | if (e.button === 1) { | ||
71 | element.classList.add('hide') | ||
72 | } | ||
73 | } | ||
74 | |||
75 | // Add draggable instance | ||
76 | const { left, top } = element.getBoundingClientRect() | ||
77 | const draggable = new PlainDraggable(element, { | ||
78 | top: top, | ||
79 | left: left, | ||
80 | handle: draggablePart, | ||
81 | snap: { x: { step: 20 }, y: { step: 20 } }, | ||
82 | }) | ||
83 | |||
84 | // FIXME use pure CSS to hide utils | ||
85 | const utils = element.querySelector('.utils') | ||
86 | const siblings = Array.from(element.parentElement.querySelectorAll(':scope > *')) | ||
87 | draggable.onDragStart = () => { | ||
88 | utils.style.opacity = 0 | ||
89 | element.classList.add('drag') | ||
90 | // Remove z-index from previous dragged | ||
91 | siblings.forEach(e => e.style.removeProperty('z-index')) | ||
92 | } | ||
93 | |||
94 | draggable.onDragEnd = () => { | ||
95 | utils.style = '' | ||
96 | element.classList.remove('drag') | ||
97 | // Ensuer last dragged block always on top | ||
98 | element.style.zIndex = '9000' | ||
99 | } | ||
100 | |||
101 | // Reposition draggable instance when resized | ||
102 | new ResizeObserver(() => { | ||
103 | try { | ||
104 | draggable.position(); | ||
105 | } catch (_) { | ||
106 | null | ||
107 | } | ||
108 | }).observe(element); | ||
109 | |||
110 | // Callback for remove | ||
111 | onRemove(element, () => { | ||
112 | draggable.remove() | ||
113 | }) | ||
114 | } | ||
115 | |||
116 | enterHandler = ({ htmlHolder, blocks }) => { | ||
117 | |||
118 | // FIXME It is weird rect from this method and this scope are different... | ||
119 | blocks.forEach(this.saveLeftTopAsData) | ||
120 | |||
121 | // Create draggable blocks and set each position by previous one | ||
122 | let [left, top] = [20, 20] | ||
123 | blocks.forEach(block => { | ||
124 | const originLeft = Number(block.getAttribute('data-left')) | ||
125 | const originTop = Number(block.getAttribute('data-top')) | ||
126 | |||
57 | // Create draggable block | 127 | // Create draggable block |
58 | const draggableBlock = document.createElement('div') | 128 | const wrapper = document.createElement('div') |
59 | draggableBlock.classList.add('draggable-block') | 129 | wrapper.classList.add('draggable-block') |
60 | draggableBlock.innerHTML = ` | 130 | wrapper.innerHTML = ` |
61 | <div class="draggable"> | ||
62 | <div class="handle">\u2630</div> | ||
63 | </div> | ||
64 | <div class="utils"> | 131 | <div class="utils"> |
65 | <div id="close">\u274C</div> | 132 | <div id="close">\u274C</div> |
66 | <div id="plus-font-size" ">\u2795</div> | 133 | <div id="plus-font-size" ">\u2795</div> |
@@ -68,82 +135,39 @@ export class Overlay extends Layout { | |||
68 | </div> | 135 | </div> |
69 | ` | 136 | ` |
70 | 137 | ||
71 | // Add draggable part | 138 | wrapper.appendChild(block) |
72 | const draggablePart = draggableBlock.querySelector('.draggable') | 139 | htmlHolder.appendChild(wrapper) |
73 | draggablePart.title = 'Use middle-click to remove block' | 140 | wrapper.style.left = left + "px" |
74 | draggablePart.onmouseup = (e) => { | 141 | wrapper.style.top = top + "px" |
75 | if (e.button === 1) { | 142 | const { width } = wrapper.getBoundingClientRect() |
76 | // Hide block with middle click | 143 | left += width + 30 |
77 | draggableBlock.setAttribute("data-state", "hide") | 144 | if (left > window.innerWidth) { |
78 | } | 145 | top += 200 |
146 | left = left % window.innerWidth | ||
79 | } | 147 | } |
80 | 148 | ||
81 | // Set elements | 149 | animateRectTransition( |
82 | draggableBlock.appendChild(draggablePart) | 150 | wrapper, |
83 | draggableBlock.appendChild(block) | 151 | { left: originLeft, top: originTop }, |
84 | container.appendChild(draggableBlock) | 152 | { resume: true, duration: 500 } |
85 | 153 | ) | |
86 | // Add draggable instance | 154 | .finished |
87 | const draggableInstance = new PlainDraggable(draggableBlock, { | 155 | .finally(() => this.addDraggable(wrapper)) |
88 | handle: draggablePart, | ||
89 | snap: { x: { step: 20 }, y: { step: 20 } }, | ||
90 | }) | ||
91 | 156 | ||
92 | // Close button | 157 | // Close button |
93 | draggableBlock.querySelector('#close').onclick = () => { | 158 | wrapper.querySelector('#close').onclick = () => { |
94 | draggableBlock.classList.add('hide') | 159 | wrapper.classList.add('hide') |
95 | } | 160 | } |
96 | // Plus/Minus font-size of content | 161 | // Plus/Minus font-size of content |
97 | draggableBlock.querySelector('#plus-font-size').onclick = () => { | 162 | wrapper.querySelector('#plus-font-size').onclick = () => { |
98 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16 | 163 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16 |
99 | block.style.fontSize = `${fontSize + 0.1}rem` | 164 | block.style.fontSize = `${fontSize + 0.1}rem` |
100 | } | 165 | } |
101 | draggableBlock.querySelector('#minus-font-size').onclick = () => { | 166 | wrapper.querySelector('#minus-font-size').onclick = () => { |
102 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16 | 167 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16 |
103 | block.style.fontSize = `${fontSize - 0.1}rem` | 168 | block.style.fontSize = `${fontSize - 0.1}rem` |
104 | } | 169 | } |
105 | 170 | }) | |
106 | // FIXME use pure CSS to hide utils | ||
107 | const utils = draggableBlock.querySelector('.utils') | ||
108 | draggableInstance.onDragStart = () => { | ||
109 | utils.style.opacity = 0 | ||
110 | draggableBlock.classList.add('drag') | ||
111 | } | ||
112 | draggableInstance.onDragEnd = () => { | ||
113 | utils.style = '' | ||
114 | draggableBlock.classList.remove('drag') | ||
115 | } | ||
116 | |||
117 | // Reposition draggable instance when resized | ||
118 | new ResizeObserver(() => { | ||
119 | try { | ||
120 | draggableInstance.position(); | ||
121 | } catch (_) { | ||
122 | null | ||
123 | } | ||
124 | }).observe(draggableBlock); | ||
125 | |||
126 | // Callback for remove | ||
127 | onRemove(draggableBlock, () => { | ||
128 | draggableInstance.remove() | ||
129 | }) | ||
130 | |||
131 | return draggableInstance | ||
132 | } | ||
133 | |||
134 | // Create draggable blocks and set each position by previous one | ||
135 | let [x, y] = [0, 0] | ||
136 | dumbymap.blocks.map(moveIntoDraggable) | ||
137 | .forEach(draggable => { | ||
138 | draggable.left = x | ||
139 | draggable.top = y | ||
140 | const rect = draggable.element.getBoundingClientRect() | ||
141 | x += rect.width + 30 | ||
142 | if (x > window.innerWidth) { | ||
143 | y += 200 | ||
144 | x = x % window.innerWidth | ||
145 | } | ||
146 | }) | ||
147 | } | 171 | } |
148 | 172 | ||
149 | leaveHandler = ({ htmlHolder, blocks }) => { | 173 | leaveHandler = ({ htmlHolder, blocks }) => { |
diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css index e04b6bc..cb692ae 100644 --- a/src/css/dumbymap.css +++ b/src/css/dumbymap.css | |||
@@ -262,8 +262,6 @@ | |||
262 | font-size: 0.8rem; | 262 | font-size: 0.8rem; |
263 | pointer-events: auto; | 263 | pointer-events: auto; |
264 | 264 | ||
265 | transition: visibility 1s; | ||
266 | |||
267 | .dumby-block { | 265 | .dumby-block { |
268 | overflow: auto; | 266 | overflow: auto; |
269 | margin: 0; | 267 | margin: 0; |
@@ -305,7 +303,7 @@ | |||
305 | } | 303 | } |
306 | } | 304 | } |
307 | 305 | ||
308 | .draggable { | 306 | .draggable-part { |
309 | display: block; | 307 | display: block; |
310 | overflow: clip; | 308 | overflow: clip; |
311 | width: 100%; | 309 | width: 100%; |
@@ -337,7 +335,7 @@ | |||
337 | visibility: hidden; | 335 | visibility: hidden; |
338 | } | 336 | } |
339 | 337 | ||
340 | &:has(.draggable:hover, .utils:hover), | 338 | &:has(.draggable-part:hover, .utils:hover), |
341 | &.drag { | 339 | &.drag { |
342 | .dumby-block { | 340 | .dumby-block { |
343 | color: gray; | 341 | color: gray; |