angular 2+ vertically resize div

14,375

Solution 1

I think you miss the part where you keep the old value of the height, plus check the state on mouseup and also to listen to mousedown. I didn't make it to a directive in the example bellow, but it would be complicated.

Stackblitz example

Typescript:

  height = 150;
  y = 100;
  oldY = 0;
  grabber = false;

  @HostListener('document:mousemove', ['$event'])
  onMouseMove(event: MouseEvent) {
    if (!this.grabber) {
        return;
    }
    this.resizer(event.clientY - this.oldY);
    this.oldY = event.clientY;
  }

  @HostListener('document:mouseup', ['$event'])
  onMouseUp(event: MouseEvent) {
    this.grabber = false;
  }

  resizer(offsetY: number) {
    this.height += offsetY;
  }


  @HostListener('document:mousedown', ['$event'])
  onMouseDown(event: MouseEvent) {
    this.grabber = true;
    this.oldY = event.clientY;
    event.preventDefault();
  }

HTML

<div class="textarea" [style.height.px]='height' contenteditable="true" >
  this is a text area
  <div class="grabber"></div>  
</div>

Solution 2

Using @Vega's Answer - a directive.

import { Directive, HostListener, ElementRef, OnInit } from '@angular/core';

@Directive({
  selector: '[resizer]'
})
export class NgxResizerDirective implements OnInit {

  height: number;
  oldY = 0;
  grabber = false;

  constructor(private el: ElementRef) { }

  @HostListener('document:mousemove', ['$event'])
  onMouseMove(event: MouseEvent) {

    if (!this.grabber) {
      return;
    }

    this.resizer(event.clientY - this.oldY);
    this.oldY = event.clientY;
  }

  @HostListener('document:mouseup', ['$event'])
  onMouseUp(event: MouseEvent) {
    this.grabber = false;
  }

  resizer(offsetY: number) {
    this.height += offsetY;
    this.el.nativeElement.parentNode.style.height = this.height + "px";
  }

  @HostListener('mousedown', ['$event']) onResize(event: MouseEvent, resizer?: Function) {
    this.grabber = true;
    this.oldY = event.clientY;
    event.preventDefault();
  }

  ngOnInit() {
    this.height = parseInt(this.el.nativeElement.parentNode.offsetHeight);
  }

}

HTML

<div class="textarea" contenteditable="true">
  this is a text area
  <div class="grabber" resizer contenteditable="false" ></div>
</div>

Solution 3

Try this:

Add a variable:

private canResize = false;

On mousedown enable resize by setting canResize to true:

@HostListener('mousedown', ['$event']) enableResize(e) {
    this.canResize = true;
    event.preventDefault();
}

so that you resize only when mouse is down:

@HostListener('window:mousemove', ['$event']) resize(e) {
  if (this.canResize) {
    this.el.nativeElement.parentNode.style.height = (e.clientY - this.el.nativeElement.parentNode.offsetTop) + 'px';
  }
  event.preventDefault();
}

On mouse up set canResize to false to disable resizing:

@HostListener('window:mouseup', ['$event']) stopResize(e) {
    this.canResize = false;
    event.preventDefault();
}

Also, take a look at this

(UPDATE: created stackblitz)

Solution 4

I found this library extremely useful and easy to use : angular-split

Usage is as given below (Below example is taken from an app component with a navigation menu on the left and individual components/pages are rendered on right - A highly used use case if you like a vertical navigation menu.)

<as-split direction="horizontal" style="height: 1000px;">
        <as-split-area size="15">
            <app-nav-menu></app-nav-menu>
        </as-split-area>
        <as-split-area size="85">
            <div class="col-12 col-lg-9 body-content">
                <router-outlet></router-outlet>
            </div>
        </as-split-area>
</as-split>

Solution 5

I've little bit improved @Sibiraj's and @Vega's answers. The main issue with the suggested solution is performance:

@HostListener('document:mousemove', ['$event'])
  onMouseMove(event: MouseEvent) {

    if (!this.grabber) {
      return;
    }

    this.resizer(event.clientY - this.oldY);
    this.oldY = event.clientY;
  }

You have to understand that @HostListener('document:mousemove', ['$event']) will be fired every mouse move on your document, you can avoid this adding mousemove listener after mousedown event and cancel it after 'mouseup' event. Please see my version:

import {Directive, ElementRef, HostListener, OnDestroy, OnInit} from '@angular/core';
import {fromEvent, Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';

@Directive({
  selector: '[resizer]'
})
export class ResizeDirective implements OnInit, OnDestroy {
    height: number;
    oldY = 0;
    grabber = false;
    destroy$ = new Subject();

    constructor(private el: ElementRef) { }

    ngOnInit() {
        this.height = parseInt(this.el.nativeElement.parentNode.offsetHeight, 10);
    }

    @HostListener('document:mouseup', ['$event'])
    onMouseUp(): void {
        this.grabber = false;
        this.destroy$.next();
    }

    @HostListener('mousedown', ['$event']) onResize(event: MouseEvent, resizerCallback?: Function) {
        this.grabber = true;
        this.oldY = event.clientY;
        event.preventDefault();

        this.addMouseMoveListener();
    }

    resizer(offsetY: number): void {
        this.height += offsetY;
        this.el.nativeElement.parentNode.style.height = this.height + 'px';
    }

    addMouseMoveListener(): void {
        fromEvent(document, 'mousemove')
            .pipe(takeUntil(this.destroy$))
            .subscribe(this.mouseMoveCallback.bind(this));
    }

    mouseMoveCallback(event: MouseEvent): void {
        if (!this.grabber) {
            return;
        }

        this.resizer(event.clientY - this.oldY);
        this.oldY = event.clientY;
    }

    ngOnDestroy() {
        this.destroy$.next();
    }

}
Share:
14,375
Sibiraj
Author by

Sibiraj

Senior FrontEnd Engineer at HappyFox Inc.

Updated on June 19, 2022

Comments

  • Sibiraj
    Sibiraj almost 2 years

    I am new to angular2 and I have been trying to create a resizable div (vertically). but I am unable to achieve that. What I have tried using a directive

    and this is my directive

    import { Directive, HostListener, ElementRef, Input } from '@angular/core';
    
    @Directive({
      selector: '[appNgxResizer]'
    })
    export class NgxResizerDirective {
    
      constructor(private el: ElementRef) {
      }
    
      @HostListener('mousemove', ['$event']) resize(e) {
        this.el.nativeElement.parentNode.style.height = (e.clientY - this.el.nativeElement.parentNode.offsetTop) + 'px';
        event.preventDefault();
      }
    
      @HostListener('mouseup', ['$event']) stopResize(e) {
        event.preventDefault();
      }
    }
    

    Here is the stackblitz for what I have tried https://stackblitz.com/edit/angular-text-resizable-q6ddyy

    I want to click grab to resize the div. Something like this https://jsfiddle.net/zv2ep6eo/.

    Thanks.