使用 CDK 拖放中的示例,我想添加左侧和顶部位置的拖动元素的预览,而无需变换样式。
<div class="example-boundary">
<div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
I can only be dragged within the dotted container
</div>
</div>
<button> Preview the dragged element </buttona>
import {Component} from '@angular/core';
import {CdkDrag} from '@angular/cdk/drag-drop';
/**
* @title Drag&Drop boundary
*/
@Component({
selector: 'cdk-drag-drop-boundary-example',
templateUrl: 'cdk-drag-drop-boundary-example.html',
styleUrls: ['cdk-drag-drop-boundary-example.css'],
standalone: true,
imports: [CdkDrag],
})
export class CdkDragDropBoundaryExample {}
当你拖动元素时,DOM 中就会有这个 div
<div _ngcontent-ng-c2320506461="" class="example-boundary">
<div _ngcontent-ng-c2320506461="" cdkdragboundary=".example-boundary" cdkdrag="" class="cdk-drag example-box" style="transform: translate3d(202px, -2px, 0px);">
I can only be dragged within the dotted container
</div>
</div>
当您拖动元素并单击预览按钮时,它应该打开预览元素,如下所示。
<div class="example-boundary">
<div class="example-box" style="left: 96.13%; top: 9.92%; display: block;">
Now I can't be dragged, sorry
</div>
</div>
意味着变换样式应该替换为左侧和顶部位置。
@angular/cdk/drag-drop
内部使用变换属性来放置盒子。无论是使用top
、left
和position
属性还是在封装的内部细节中使用transform
,都应该理解为封装。使用这两种方法都可以获得相同的结果。如果您想对 top
和 left
属性执行任何特定操作,您可以根据 transform
和元素原始位置或纯 JS 中的代码拖动功能来计算它们。
以下是你想要做的纯JS版本。
const box = document.querySelector('.example-box');
const boundary = document.querySelector(box.getAttribute('cdkDragBoundary') || '');
const posDisplay = document.querySelector('#pos');
const offset = {
x: 0,
y: 0
};
const onMouseMove = (e) => {
e.preventDefault();
const [cx, cy] = [e.clientX, e.clientY];
const {
width,
height
} = box.getBoundingClientRect();
let top = cy - offset.y;
let left = cx - offset.x;
const {
width: bw,
height: bh
} = boundary?.getBoundingClientRect();
top = Math.min(top, (bh || innerHeight) - height);
top = Math.max(top, 0);
left = Math.min(left, (bw || innerWidth) - width);
left = Math.max(left, 0);
box.style.top = top + 'px';
box.style.left = left + 'px';
posDisplay.innerText = `left: ${left}px, top: ${top}px`;
};
box.onmousedown = e => {
e.preventDefault();
offset.x = e.clientX - box.offsetLeft;
offset.y = e.clientY - box.offsetTop;
window.onmousemove = onMouseMove;
window.onmouseup = () => {
window.onmousemove = null;
window.onmouseup = null;
};
}
.example-boundary {
position: relative;
border: 1px dotted gray;
width: 80vw;
height: 80vh;
margin: 0 10vmin;
}
.example-box {
position: absolute;
width: 200px;
padding: 10px;
border-radius: 10px;
border: 1px solid green;
cursor: grab;
}
#pos {
height: 50px;
padding: 0 10px;
}
<p id="pos">left: 0, top: 0</p>
<div class="example-boundary">
<div class="example-box" cdkDragBoundary=".example-boundary">
I can only be dragged within the dotted container
</div>
</div>
我制作了以下两个工作示例,可以满足您的要求。在这两种情况下,
top
和 left
位置都是使用以下 正则表达式 通过捕获组 1 和 2 transform:\s*translate3d\((.+?),(.+?),.+?\)
来计算的。
以下代码克隆 Angular 生成的 HTML 并手动删除不需要的属性。代码中的注释中有更多详细信息。
如果您只是想重新渲染拖到其他地方的内容,我个人建议使用此方法,因为它消除了管理属性的复杂性。
/**
* 1. Added a flag called `isPreviewShown` which toggles which HTML is visible, either the preview or the original
* 2. Added a method called `showPreview` which clones the draggable contents and then manually removes unwanted attributes (you'll have to maintain this logic, so you may remove other attributes you don't need)
* 3. Replaced `cssText` style attributes from `translate3d` to `top/left` values. I used the following [regular expression][2] for it `transform:\s*translate3d\((.+?),(.+?),.+?\)` and the replaced it to `left:$1; top:$2`
**/
isPreviewShown = false;
showPreview() {
// clone the contents
this.previewContainer.nativeElement.innerHTML = this.exampleBoundary.nativeElement.innerHTML;
// clear unwanted attributes
document.querySelectorAll('.preview-container *').forEach((content: HTMLElement) => {
const attrs = content.getAttributeNames();
for (var attr in attrs) {
if (
attrs[attr].indexOf('cdk') !== -1 // <-- remove cdk related attributes
|| attrs[attr].indexOf('_ng') === 0 // <-- remove angular relates attributes
|| attrs[attr].indexOf('ng-') === 0 // <-- remove more angular relates attributes
) {
console.log('->> removed: ', attrs[attr])
content.removeAttribute(attrs[attr]);
}
}
// transform/translate3d --> top/left
content.style.cssText = content.style.cssText.replace(/transform:\s*translate3d\((.+?),(.+?),.+?\)/i, 'left:$1; top:$2')
// remove cdk-drag class
content.classList.remove('cdk-drag')
});
console.log('>> Result: ', this.previewContainer.nativeElement.innerHTML)
// show the preview
this.isPreviewShown = true;
this.cdr.detectChanges();
}
<!--
1. Added a `preview-container` which is where the preview is going to be shown
2. Added a `main-container` which is meant to make the `preview` to overlap the original content
3. Added an extra button called "Hide preview element" which hides the preview so you can drag again
-->
<div class="main-container">
<div class="example-boundary" #exampleBoundary>
<div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
I can only be dragged within the dotted container
</div>
</div>
<div class="preview-container example-box" #previewContainer [hidden]="!isPreviewShown">
</div>
</div>
<br>
<button (click)="hidePreview()" [hidden]="!isPreviewShown"> Hide preview element </button>
<button (click)="showPreview()" [hidden]="isPreviewShown"> Preview the dragged element </button>
/**
* 1. Add `position: relative` to the `main-container` so the `preview-container` size and position are shown relative to the main container.
* 2. Had to add `:host ::ng-deep` to class `.example-box` so the CSS can be shared by both preview and original content (an alternative to this, is to set all style attributes inline by using [`ngStyle`][4] so you'll don't need a css class)
*/
:host ::ng-deep .example-box {
/* no changes here */
}
.main-container {
position: relative;
width: 400px;
height: 400px;
}
.preview-container {
position: absolute;
background: white;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
z-index: 1;
}
这种方法完全按照您在注释中的要求,它将 DOM 位置、宽度、高度等存储在变量中,然后通过迭代变量来渲染它。
如果您稍后要操作单个项目(例如添加拖动时不存在的可配置内容),那么这种方法会比 #1 更好。
/**
* 1. Added an interface called `IDragAndDropItem` with all the attributes that are going to be stored.
* 2. Added a flag called `isPreviewShown` which toggles which HTML is visible, either the preview or the original
* 3. Added a method called `showPreview` which iterates the first DOM level and stores the desired attributes in the array `previewItems`
* 4. Computed the `top` and `left` values from the `cssText` style attribute, matched the following [regular expression][2] captured group 1 and 2: `transform:\s*translate3d\((.+?),(.+?),.+?\)`
*/
export interface IDragAndDropItem {
width: string;
height: string;
left: string | null;
top: string | null;
text: string;
}
isPreviewShown = false;
previewItems: Array<IDragAndDropItem> = [];
showPreview() {
this.previewItems = [];
// save the contents
document
.querySelectorAll('.example-boundary > *') // <-- purposely doesn't support nested DOM
.forEach((content: HTMLElement) => {
const position = content.style.cssText.match(
/transform:\s*translate3d\((.+?),(.+?),.+?\)/i
);
this.previewItems.push({
width: content.offsetWidth + 'px',
height: content.offsetHeight + 'px',
left: position ? position[1] || null : null,
top: position ? position[2] || null : null,
text: content.innerText
})
});
// show the preview
this.isPreviewShown = true;
this.cdr.detectChanges();
// show html
this.resultHtml.nativeElement.innerText =
this.previewContainer.nativeElement.innerHTML;
}
<!--
1. Added a `preview-container` which is where the `previewItems` is iterated and rendered.
2. Added a `main-container` which is meant to make the `preview` to overlap the original content
3. Added an extra button called "Hide preview element" which hides the preview so you can drag again
-->
<div class="main-container">
<div class="example-boundary" #exampleBoundary>
<div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
I can only be dragged within the dotted container
</div>
</div>
<div class="preview-container example-boundary" #previewContainer [hidden]="!isPreviewShown">
<div class="preview-box" *ngFor="let item of previewItems" [ngStyle]="{left: item.left, top: item.top, width: item.width, height: item.height}">
{{ item.text }}
</div>
</div>
</div>
<br>
<button (click)="hidePreview()" [hidden]="!isPreviewShown"> Hide preview element </button>
<button (click)="showPreview()" [hidden]="isPreviewShown"> Preview the dragged element </button>
/**
* 1. Add `position: relative` to the `main-container` so the `preview-container` size and position are shown relative to the main container.
* 2. Had to share styles from `example-box` with `preview-box`, otherwise every `style` attribute should be also stored.
*/
.example-box, .preview-box {
/* no changes here */
}
.main-container {
position: relative;
width: 400px;
height: 400px;
}
.preview-container {
position: absolute;
background: white;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
z-index: 1;
}