如何删除可拖动元素中的变换样式?

问题描述 投票:0回答:2

使用 CDK 拖放中的示例,我想添加左侧和顶部位置的拖动元素的预览,而无需变换样式。

HTML

<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>

意味着变换样式应该替换为左侧和顶部位置。

javascript css node.js draggable angular13
2个回答
5
投票

@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>


1
投票

TLDR;

我制作了以下两个工作示例,可以满足您的要求。在这两种情况下,

top
left
位置都是使用以下 正则表达式 通过捕获组 1 和 2
transform:\s*translate3d\((.+?),(.+?),.+?\)
来计算的。

  1. 克隆 DOM,这是我推荐的答案,它克隆 Angular 生成的 HTML 并手动删除不需要的属性。

  2. 存储数据,按照您的要求,它将 DOM 位置、宽度、高度等存储在变量中,然后进行渲染。


方法#1(克隆 DOM

以下代码克隆 Angular 生成的 HTML 并手动删除不需要的属性。代码中的注释中有更多详细信息。

如果您只是想重新渲染拖到其他地方的内容,我个人建议使用此方法,因为它消除了管理属性的复杂性。

TS:

/**
 * 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();
}

HTML:

<!--
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>

CSS:

/**
 * 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;
}

方法#2(存储数据

这种方法完全按照您在注释中的要求,它将 DOM 位置、宽度、高度等存储在变量中,然后通过迭代变量来渲染它。

如果您稍后要操作单个项目(例如添加拖动时不存在的可配置内容),那么这种方法会比 #1 更好。

TS:

/**
 * 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;
}

HTML:

<!--
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>

CSS:

/**
 * 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;
}
© www.soinside.com 2019 - 2024. All rights reserved.