aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-09-22 17:52:48 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-09-22 17:58:09 +0800
commit0a1d70dd6017164e13386997b7b03eab47045e0a (patch)
tree289214c209ca8a399139b75f70934485079acd89
parent516aab9de3d6ad5b52da75666ce9f6531c913be6 (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.mjs172
-rw-r--r--src/css/dumbymap.css6
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 @@
1import PlainDraggable from 'plain-draggable' 1import PlainDraggable from 'plain-draggable'
2import { onRemove } from './utils' 2import { onRemove, animateRectTransition } from './utils'
3 3
4export class Layout { 4export 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 {
51export class Overlay extends Layout { 52export 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;