각도 방향으로 자유롭게 드래그하기 위한 명령 생성

본고에서 우리는 Angular에서 명령을 만드는 방법을 배워서 제3자 라이브러리를 사용하지 않고 요소를 자유롭게 드래그할 수 있도록 할 것이다.

인코딩을 시작해보도록 하겠습니다.


1 기본 자유 드래그 명령 만들기


우리는 우선 간단한 기본 명령을 만들고 더 많은 기능을 추가할 것이다.

1.1 작업공간 만들기


npm i -g @angular/cli
ng new angular-free-dragging --defaults --minimal

Do not use --minimal option in production applications, it creates a workspace without any testing frameworks. You can read more about CLI options.


1.2 공유 모듈 만들기


ng g m shared

1.3.1 자유 드래그 명령 만들기


ng g d shared/free-dragging

1.3.2 수출지령


만든 후 공유 모듈의 내보내기 배열에 추가합니다.
// src/app/shared/shared.module.ts

import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { FreeDraggingDirective } from "./free-dragging.directive";

@NgModule({
  declarations: [FreeDraggingDirective],
  imports: [CommonModule],
  exports: [FreeDraggingDirective], // Added
})
export class SharedModule {}

1.3.3 자유 드래그 논리


자유롭게 드래그하려면 다음과 같이 하십시오.
  • 감청 요소의mousedown 사건.이것은 드래그 시작 트리거로 사용할 것입니다.
  • 문서의 mousemove 이벤트를 듣습니다.이것은 드래그 트리거로 작동합니다.또한 마우스 포인터를 기준으로 요소의 위치도 업데이트됩니다.
  • 문서의 mouseup 이벤트를 듣습니다.이것은 드래그 끝 트리거로 사용할 것입니다.이것으로 우리는 청취를 중지할 것이다mousemove사건.
  • 상기 모든 청중에게 우리는 관찰할 수 있는 것을 만들 것이다.그러나 우선 우리의 지령을 설정하자.
    // src/app/shared/free-dragging.directive.ts
    
    @Directive({
      selector: "[appFreeDragging]",
    })
    export class FreeDraggingDirective implements OnInit, OnDestroy {
      private element: HTMLElement;
    
      private subscriptions: Subscription[] = [];
    
      constructor(
        private elementRef: ElementRef,
        @Inject(DOCUMENT) private document: any
      ) {}
    
      ngOnInit(): void {
        this.element = this.elementRef.nativeElement as HTMLElement;
        this.initDrag();
      }
    
      initDrag(): void {
        // main logic will come here
      }
    
      ngOnDestroy(): void {
        this.subscriptions.forEach((s) => s.unsubscribe());
      }
    }
    
    
    위 코드에서 우리는 주로 세 가지 일을 한다.
  • 나중에 위치를 변경할 수 있도록 원본 HTML 요소를 가져옵니다.
  • 모든 드래그를 시작합니다. 자세한 내용은 곧 확인할 수 있습니다.
  • 삭제할 때 저희는 구독을 취소하여 자원을 방출하고 있습니다.
  • 드래그 함수를 작성합니다.
    // src/app/shared/free-dragging.directive.ts
    
    ...
    
      initDrag(): void {
        // 1
        const dragStart$ = fromEvent<MouseEvent>(this.element, "mousedown");
        const dragEnd$ = fromEvent<MouseEvent>(this.document, "mouseup");
        const drag$ = fromEvent<MouseEvent>(this.document, "mousemove").pipe(
          takeUntil(dragEnd$)
        );
    
        // 2
        let initialX: number,
          initialY: number,
          currentX = 0,
          currentY = 0;
    
        let dragSub: Subscription;
    
        // 3
        const dragStartSub = dragStart$.subscribe((event: MouseEvent) => {
          initialX = event.clientX - currentX;
          initialY = event.clientY - currentY;
          this.element.classList.add('free-dragging');
    
          // 4
          dragSub = drag$.subscribe((event: MouseEvent) => {
            event.preventDefault();
    
            currentX = event.clientX - initialX;
            currentY = event.clientY - initialY;
    
            this.element.style.transform =
              "translate3d(" + currentX + "px, " + currentY + "px, 0)";
          });
        });
    
        // 5
        const dragEndSub = dragEnd$.subscribe(() => {
          initialX = currentX;
          initialY = currentY;
          this.element.classList.remove('free-dragging');
          if (dragSub) {
            dragSub.unsubscribe();
          }
        });
    
        // 6
        this.subscriptions.push.apply(this.subscriptions, [
          dragStartSub,
          dragSub,
          dragEndSub,
        ]);
      }
    
    ...
    
  • 우리는 탐지기를 위해 3개의 관찰 대상을 만들고 있는데 이것은 우리가 이전에 사용한 [fromEvent](https://rxjs.dev/api/index/function/fromEvent) 함수에서 본 것이다.
  • 그리고 보조 변수를 만들었습니다. 이 변수는 요소의 위치를 업데이트하는 데 사용됩니다.
  • 다음에 우리는 mousedown 우리 요소에 관한 사건을 들을 것이다.사용자가 마우스를 누르면, 우리는 초기 위치를 저장하고, 클래스 free-dragging 를 추가합니다. 이 클래스는 요소에 아름다운 음영을 추가합니다.
  • 사용자가 요소를 눌렀을 때만 이동하려고 합니다. 이것이 바로 우리가 mousemove 이벤트의 구독 서버에서 mousedown 이벤트를 감청하는 이유입니다.사용자가 마우스를 이동할 때, 우리도 transform 속성을 사용하여 마우스의 위치를 업데이트합니다.
  • 우리는 mouseup 사건을 듣고 있다.여기서 다음 드래그를 위해 초기 위치를 다시 업데이트합니다.free-dragging 클래스를 삭제하고 있습니다.
  • 마지막으로 모든 구독을 전송하고 있습니다. 그러면 all inngOnDestroy에서 구독을 취소할 수 있습니다.
  • AppComponent에서 시도해 볼 때가 되었다.

    1.3.4 AppComponent 업데이트


    컨텐트를 다음과 같이 바꿉니다.
    // src/app/app.component.ts
    
    import { Component } from "@angular/core";
    
    @Component({
      selector: "app-root",
      // 1 use directive
      template: ` <div class="example-box" appFreeDragging>Drag me around</div> `,
      // 2 some helper styles
      styles: [
        `
          .example-box {
            width: 200px;
            height: 200px;
            border: solid 1px #ccc;
            color: rgba(0, 0, 0, 0.87);
            cursor: move;
            display: flex;
            justify-content: center;
            align-items: center;
            text-align: center;
            background: #fff;
            border-radius: 4px;
            position: relative;
            z-index: 1;
            transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
            box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
              0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
          }
    
          .example-box.free-dragging {
            box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
          }
        `,
      ],
    })
    export class AppComponent {}
    
    
    위의 코드는 매우 간단명료하다.이를 실행하려면 다음과 같이 하십시오.
    ng serve
    
    출력을 보려면 다음과 같이 하십시오.

    현재 명령에서는 요소의 아무 곳에서나 마우스를 누른 채 이동하여 요소를 드래그할 수 있습니다.이렇게 하는 것은 텍스트 선택 등 다른 조작에서 매우 어렵다는 단점이 있다.더 실제적인 장면에서, 예를 들어 작은 위젯 같은 경우, 드래그를 편리하게 하기 위해 손잡이가 필요하다.

    2. 드래그 핸들 지원 추가


    우리는 명령을 다시 만들고 주 명령에 @ContentChild 접근해서 드래그 핸들에 대한 지원을 추가할 것입니다.

    2.1 드래그 핸들에 대한 명령 생성


    ng g d shared/free-dragging-handle
    

    2.2 공유 모듈에서 내보내기


    // src/app/shared/shared.module.ts
    
    import { NgModule } from "@angular/core";
    import { CommonModule } from "@angular/common";
    import { FreeDraggingDirective } from "./free-dragging.directive";
    import { FreeDraggingHandleDirective } from './free-dragging-handle.directive';
    
    @NgModule({
      declarations: [FreeDraggingDirective, FreeDraggingHandleDirective],
      imports: [CommonModule],
      exports: [FreeDraggingDirective, FreeDraggingHandleDirective], // Modified
    })
    export class SharedModule {}
    
    

    2.3 드래그 핸들에서 ElementRef로 돌아가기


    우리는 draghandle의 요소만 있으면 다음 작업을 완성할 수 있으며 ElementRef 를 사용하여 같은 기능을 실현할 수 있다.
    // src/app/shared/free-dragging-handle.directive.ts
    
    import { Directive, ElementRef } from "@angular/core";
    
    @Directive({
      selector: "[appFreeDraggingHandle]",
    })
    export class FreeDraggingHandleDirective {
      constructor(public elementRef: ElementRef<HTMLElement>) {} // Modified
    }
    
    

    2.4 손잡이로 드래그


    논리는 다음과 같습니다.
  • 주 요소
  • 에서 하위 드래그 핸들 요소 가져오기
  • 문단 원소의 mousedown 사건을 탐지하다.이것은 드래그 시작 트리거로 사용할 것입니다.
  • 문서의 mousemove 이벤트를 듣습니다.이것은 드래그 트리거로 작동합니다.또한 마우스 포인터에 따라 핸들 요소뿐만 아니라 주 요소의 위치도 업데이트됩니다.
  • 문서의 mouseup 이벤트를 듣습니다.이것은 드래그 끝 트리거로 사용할 것입니다.이것으로 우리는 청취를 중지할 것이다mousemove사건.
  • 그래서 기본적으로 유일한 변화는 요소를 바꾸는 것이다. 우리는 그 중에서 mousedown 사건을 들을 것이다.
    인코딩으로 돌아가겠습니다.
    // src/app/shared/free-dragging.directive.ts
    
    ...
    
    @Directive({
      selector: "[appFreeDragging]",
    })
    export class FreeDraggingDirective implements AfterViewInit, OnDestroy {
    
      private element: HTMLElement;
    
      private subscriptions: Subscription[] = [];
    
      // 1 Added
      @ContentChild(FreeDraggingHandleDirective) handle: FreeDraggingHandleDirective;
      handleElement: HTMLElement;
    
      constructor(...) {}
    
      // 2 Modified
      ngAfterViewInit(): void {
        this.element = this.elementRef.nativeElement as HTMLElement;
        this.handleElement = this.handle?.elementRef?.nativeElement || this.element;
        this.initDrag();
      }
    
      initDrag(): void {
        // 3 Modified
        const dragStart$ = fromEvent<MouseEvent>(this.handleElement, "mousedown");
    
        // rest remains same
    
      }
    
      ...
    
    }
    
    우리가 한 것은 코드가 논리에서 설명한 것과 같다.현재 우리가 사용하고 있는 것은 ngOnInit이 아니라 ngAfterViewInit입니다. 구성 요소의 보기가 완전히 초기화되었는지 확인하고 싶으므로 존재하면 FreeDraggingDirective를 얻을 수 있습니다.너는 Angular - Hooking into the component lifecycle에서 이 방면에 관한 더 많은 정보를 읽을 수 있다.

    2.5 AppComponent 업데이트


    // src/app/app.component.ts
    
    @Component({
      selector: "app-root",
      template: `
        <!-- 1 use directive -->
        <div class="example-box" appFreeDragging>
          I can only be dragged using the handle
    
          <!-- 2 use handle directive -->
          <div class="example-handle" appFreeDraggingHandle>
            <svg width="24px" fill="currentColor" viewBox="0 0 24 24">
              <path
                d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"
              ></path>
              <path d="M0 0h24v24H0z" fill="none"></path>
            </svg>
          </div>
        </div>
      `,
      // 3 helper styles
      styles: [
        `
          .example-box {
            width: 200px;
            height: 200px;
            padding: 10px;
            box-sizing: border-box;
            border: solid 1px #ccc;
            color: rgba(0, 0, 0, 0.87);
            display: flex;
            justify-content: center;
            align-items: center;
            text-align: center;
            background: #fff;
            border-radius: 4px;
            position: relative;
            z-index: 1;
            transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
            box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
              0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
          }
    
          .example-box.free-dragging {
            box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
          }
    
          .example-handle {
            position: absolute;
            top: 10px;
            right: 10px;
            color: #ccc;
            cursor: move;
            width: 24px;
            height: 24px;
          }
        `,
      ],
    })
    export class AppComponent {}
    
    출력을 살펴보겠습니다.

    다행이다. 우리는 우리가 필요로 하는 것을 거의 실현했다.
    그러나 그것은 여전히 문제가 있다.사용자는 요소를 뷰 외부로 이동할 수 있습니다.

    3. 경계 드래그 지원 추가


    국경에 대한 지원을 추가할 때가 되었다.경계는 요소를 원하는 영역 내에 유지하는 데 도움을 줍니다.

    3.1 업데이트 명령


    우리는 이러한 경계를 지지할 것이다.
  • 사용자 정의 경계 요소 조회를 설정하기 위해 @Input 를 추가합니다.기본적으로 우리는 그것을 body에 유지한다.
  • 우리가 querySelector 로 경계원을 얻을 수 있는지 검사합니다. 만약 오류가 발생하지 않았다면.
  • 경계 요소의 레이아웃 높이와 너비를 사용하여 드래그된 요소의 위치를 조정합니다.
  • // src/app/shared/free-dragging.directive.ts
    
    ...
    
    @Directive({
      selector: "[appFreeDragging]",
    })
    export class FreeDraggingDirective implements AfterViewInit, OnDestroy {
    
      ...
    
      // 1 Added
      private readonly DEFAULT_DRAGGING_BOUNDARY_QUERY = "body";
      @Input() boundaryQuery = this.DEFAULT_DRAGGING_BOUNDARY_QUERY;
      draggingBoundaryElement: HTMLElement | HTMLBodyElement;
    
      ...
    
      // 2 Modified
      ngAfterViewInit(): void {
        this.draggingBoundaryElement = (this.document as Document).querySelector(
          this.boundaryQuery
        );
        if (!this.draggingBoundaryElement) {
          throw new Error(
            "Couldn't find any element with query: " + this.boundaryQuery
          );
        } else {
          this.element = this.elementRef.nativeElement as HTMLElement;
          this.handleElement =
            this.handle?.elementRef?.nativeElement || this.element;
          this.initDrag();
        }
      }
    
      initDrag(): void {
        ...
    
        // 3 Min and max boundaries
        const minBoundX = this.draggingBoundaryElement.offsetLeft;
        const minBoundY = this.draggingBoundaryElement.offsetTop;
        const maxBoundX =
          minBoundX +
          this.draggingBoundaryElement.offsetWidth -
          this.element.offsetWidth;
        const maxBoundY =
          minBoundY +
          this.draggingBoundaryElement.offsetHeight -
          this.element.offsetHeight;
    
        const dragStartSub = dragStart$.subscribe((event: MouseEvent) => {
          ...
    
          dragSub = drag$.subscribe((event: MouseEvent) => {
            event.preventDefault();
    
            const x = event.clientX - initialX;
            const y = event.clientY - initialY;
    
            // 4 Update position relatively
            currentX = Math.max(minBoundX, Math.min(x, maxBoundX));
            currentY = Math.max(minBoundY, Math.min(y, maxBoundY));
    
            this.element.style.transform =
              "translate3d(" + currentX + "px, " + currentY + "px, 0)";
          });
        });
    
        const dragEndSub = dragEnd$.subscribe(() => {
          initialX = currentX;
          initialY = currentY;
          this.element.classList.remove("free-dragging");
          if (dragSub) {
            dragSub.unsubscribe();
          }
        });
    
        this.subscriptions.push.apply(this.subscriptions, [
          dragStartSub,
          dragSub,
          dragEndSub,
        ]);
      }
    }
    
    요소를 드래그할 수 있도록 body의 높이를 100%로 설정해야 합니다.
    // src/styles.css
    
    html,
    body {
      height: 100%;
    }
    
    
    이제 출력을 살펴보겠습니다.

    이렇게!영예🎉😀👍

    결론


    빠르게 수정:
    ✔️ 우리는 자유 드래그 명령을 만들었다
    ✔️ 그런 다음 사용자가 요소에 대해 다른 작업을 수행할 수 있도록 드래그 핸들에 대한 지원이 추가되었습니다.
    ✔️ 마지막으로 경계 요소를 추가하여 요소를 특정 경계 안으로 드래그하는 데 도움이 됩니다.
    ✔️ 이 모든 것은 제3자 창고가 없다😉
    추가 기능을 추가할 수 있는 기능은 다음과 같습니다.
  • 잠금 축 - 수평 또는 수직 방향으로만 드래그할 수 있음
  • 이벤트 - 드래그 시작, 드래그 및 드래그 끝과 같은 각 동작에 이벤트를 생성합니다
  • .
  • 위치 재설정 - 드래그를 원래 위치로 이동
  • 작은 위젯, 채팅 상자, 도움말, 작은 위젯 지원 등 다양한 상황에서 이 드래그 기능을 사용할 수 있습니다. 드래그 요소 (제목, 단추 등) 를 지원하는 기능을 갖춘 편집기를 구축할 수 있습니다.
    Github에 제공된 모든 코드는 다음과 같습니다.

    슈다만 / 모서리 자유 드래그


    Angular에서 모든 요소를 자유롭게 드래그할 수 있는 명령을 만듭니다. 제3자 라이브러리를 사용하지 않아도 됩니다.


    각도 방향으로 자유롭게 드래그하기 위한 명령 생성



    본고에서 우리는 Angular에서 명령을 만드는 방법을 배워서 제3자 라이브러리를 사용하지 않고 요소를 자유롭게 드래그할 수 있도록 할 것이다.

    읽다


    이 코드는 indepth에 있는 글을 위한 것입니다.데이브, Create a directive for free dragging in Angular 에서 읽을 수 있습니다.

    발전시키다


    git 클론https://github.com/shhdharmen/angular-free-dragging.git
    모서리 없는 드래그
    npm i
    npm 시작
    View on GitHub
    본문을 읽어 주셔서 감사합니다.평론 부분에서 너의 생각과 피드백을 나에게 말해라.

    크레디트


    본문을 작성할 때, 나는 w3schoolsstackoverflow의 코드 세션을 참고했다.

    좋은 웹페이지 즐겨찾기