我正在尝试为我的客户构建一个网站,这与 Miro(或其他白板应用程序)没有什么不同。
这个概念是使用绝对定位来布局各个元素,但管理员可以将它们拖动到视口中并为每个元素设置宽度和高度属性。
我无法使用任何允许拖动到原始窗口高度以下的方法(即使整个文档的元素的高度设置大于窗口高度)。
我尝试了3种方法:
const draggingClass = 'dragging';
class draggable {
constructor(element, node = [ document ] ) {
this.node = node;
this.el = element;
if ( this.el ) {
this.mouseDown = this.mouseDown.bind(this);
this.mouseUp = this.mouseUp.bind(this);
this.mouseMove = this.mouseMove.bind(this);
}
}
setHandle( handle ) {
this.handle = handle;
return this;
}
setCallback( callback ) {
this.callback = callback;
return this;
}
mouseDown( event ) {
if ( this.callback?.start ) this.callback.start( event, this.el )
else this.el.classList.add(draggingClass)
for ( const node of this.node ) {
node.addEventListener('mouseup', this.mouseUp);
node.addEventListener('mousemove', this.mouseMove);
node.addEventListener('mouseleave', this.mouseUp);
}
this.el.addEventListener('mouseleave', this.mouseUp);
}
mouseUp( event ) {
if ( this.callback?.end ) this.callback.end( event, this.el )
else this.el.classList.remove(draggingClass)
for ( const node of this.node ) {
node.removeEventListener('mouseup', this.mouseUp);
node.removeEventListener('mousemove', this.mouseMove);
node.removeEventListener('mouseleave', this.mouseUp);
}
this.el.removeEventListener('mouseleave', this.mouseUp);
}
mouseMove(event) {
if ( this.callback?.move ) {
this.callback.move( event, this.el );
} else {
const style = window.getComputedStyle(this.el);
const x = (parseFloat(style.getPropertyValue('left')) || 0) + event.movementX;
const y = (parseFloat(style.getPropertyValue('top')) || 0) + event.movementY;
const rect = this.el.getBoundingClientRect();
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const maxX = viewWidth - rect.width;
const maxY = viewHeight - rect.height;
const constrainedX = Math.max(0, Math.min(x, maxX));
const constrainedY = Math.max(0, Math.min(y, maxY));
this.el.style.position = 'absolute';
this.el.style.top = constrainedY + 'px';
this.el.style.left = constrainedX + 'px';
}
}
run() {
const handle = this.handle ?? this.el
handle.addEventListener('mousedown', this.mouseDown)
}
stop() {
const handle = this.handle ?? this.el
handle.removeEventListener('mousedown', this.mouseDown)
}
}
const boxes = document.querySelectorAll('.box');
const movable1 = new draggable(boxes[0]);
const movable2 = new draggable(boxes[1]);
movable1.run();
movable2.run();
body {
font-family: monospace;
}
.wrapper {
min-height: 400vh;
background-color: rgb(255 0 0 / 10%);
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
overflow-y: scroll;
}
.box {
position: absolute;
padding: 10px;
resize: both;
height: 150px;
width: 150px;
margin-bottom: 20px;
background: #f0f0f0;
border: 2px solid #555;
overflow: auto;
background-image: linear-gradient(-45deg, #ccc 10px, transparent 10px);
}
.second {
top: 50px;
left: 50px;
}
.third {
top: 100px;
left: 100px;
}
.heading {
background: red;
padding: 5px 10px;
}
.dragging {
opacity: 0.9;
border: 2px dashed #8181b9;
user-select: none;
z-index: 2;
cursor: move;
}
.actions {
display: flex;
flex-direction: column;
position: absolute;
right: 0;
z-index: 1;
}
<div class="outer">
<div class="wrapper">
<div class="box">
I am box 1
</div>
<div class="box second">
I am box 2
</div>
<div class="box second third dragging">
I am box 3
</div>
</div>
</div>
HTML 与上面相同,但使用此 CSS
html.no-scroll {
overflow-y: hidden;
}
.outer {
position: fixed;
top: 0;
bottom: 0;
width: 100%;
}
.wrapper {
height: 100%;
background-color: rgb(255 0 0 / 10%);
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
overflow-y: scroll;
}
.box {
position: relative; // I also added a JS toggle to switch position from relative to absolute
...
}
与前面的示例类似的 HTML 和 CSS,但这里有一些 JS
let vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
const trigger = document.querySelector('.actions button');
trigger.addEventListener('click', () => {
let root = document.querySelector(':root');
let docHeight = document.documentElement.clientHeight;
let winHeight = window.innerHeight;
docHeight = 1200; // also tried `${1200}px`
winHeight = 1300; // also tried `${1300}px`
root.style.height = `${1400}px`;
console.log(docHeight, winHeight, root.style.height);
console.log(vh);
});
我还尝试在可拖动包装器下方添加一个元素。以及在 html 和 body 上设置高度属性。似乎没有什么能让用户将框拖到原始窗口高度以下。除非我错过了一些简单的 CSS 修复。
JSFiddles 包含按原样工作的所有其他功能(拖动框),但我在第一个示例中添加了一个代码片段,这将是最容易弄清楚的,因为我认为这可能是纯 CSS。
有趣的是,如果你用谷歌搜索这个主题,似乎有大量关于限制拖动到特定容器内的答案。
如果可能的话,我宁愿避开图书馆。
https://www.google.com/search?q=javascript+drag+item+beyond+window+height+site%3Astackoverflow.com
非常感谢任何帮助。
如果我正确理解您的问题,这就是您的代码中描述此行为的方式。
请看看我如何在代码中禁用约束并将 constrained... 替换为 x/y,现在一切正常:
// const constrainedX = Math.max(0, Math.min(x, maxX));
// const constrainedY = Math.max(0, Math.min(y, maxY));
this.el.style.position = 'absolute';
this.el.style.top = y + 'px';
this.el.style.left = x + 'px';
如果这有帮助,请告诉我。
const draggingClass = 'dragging';
class draggable {
constructor(element, node = [ document ] ) {
this.node = node;
this.el = element;
if ( this.el ) {
this.mouseDown = this.mouseDown.bind(this);
this.mouseUp = this.mouseUp.bind(this);
this.mouseMove = this.mouseMove.bind(this);
}
}
setHandle( handle ) {
this.handle = handle;
return this;
}
setCallback( callback ) {
this.callback = callback;
return this;
}
mouseDown( event ) {
if ( this.callback?.start ) this.callback.start( event, this.el )
else this.el.classList.add(draggingClass)
for ( const node of this.node ) {
node.addEventListener('mouseup', this.mouseUp);
node.addEventListener('mousemove', this.mouseMove);
node.addEventListener('mouseleave', this.mouseUp);
}
this.el.addEventListener('mouseleave', this.mouseUp);
}
mouseUp( event ) {
if ( this.callback?.end ) this.callback.end( event, this.el )
else this.el.classList.remove(draggingClass)
for ( const node of this.node ) {
node.removeEventListener('mouseup', this.mouseUp);
node.removeEventListener('mousemove', this.mouseMove);
node.removeEventListener('mouseleave', this.mouseUp);
}
this.el.removeEventListener('mouseleave', this.mouseUp);
}
mouseMove(event) {
if ( this.callback?.move ) {
this.callback.move( event, this.el );
} else {
const style = window.getComputedStyle(this.el);
const x = (parseFloat(style.getPropertyValue('left')) || 0) + event.movementX;
const y = (parseFloat(style.getPropertyValue('top')) || 0) + event.movementY;
const rect = this.el.getBoundingClientRect();
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const maxX = viewWidth - rect.width;
const maxY = viewHeight - rect.height;
// const constrainedX = Math.max(0, Math.min(x, maxX));
// const constrainedY = Math.max(0, Math.min(y, maxY));
this.el.style.position = 'absolute';
this.el.style.top = y + 'px';
this.el.style.left = x + 'px';
}
}
run() {
const handle = this.handle ?? this.el
handle.addEventListener('mousedown', this.mouseDown)
}
stop() {
const handle = this.handle ?? this.el
handle.removeEventListener('mousedown', this.mouseDown)
}
}
const boxes = document.querySelectorAll('.box');
const movable1 = new draggable(boxes[0]);
const movable2 = new draggable(boxes[1]);
movable1.run();
movable2.run();
body {
font-family: monospace;
}
.wrapper {
min-height: 400vh;
background-color: rgb(255 0 0 / 10%);
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
overflow-y: scroll;
}
.box {
position: absolute;
padding: 10px;
resize: both;
height: 150px;
width: 150px;
margin-bottom: 20px;
background: #f0f0f0;
border: 2px solid #555;
overflow: auto;
background-image: linear-gradient(-45deg, #ccc 10px, transparent 10px);
}
.second {
top: 50px;
left: 50px;
}
.third {
top: 100px;
left: 100px;
}
.heading {
background: red;
padding: 5px 10px;
}
.dragging {
opacity: 0.9;
border: 2px dashed #8181b9;
user-select: none;
z-index: 2;
cursor: move;
}
.actions {
display: flex;
flex-direction: column;
position: absolute;
right: 0;
z-index: 1;
}
<div class="outer">
<div class="wrapper">
<div class="box">
I am box 1
</div>
<div class="box second">
I am box 2
</div>
<div class="box second third dragging">
I am box 3
</div>
</div>
</div>