0

I am already calculating left and top positions using getBoundingClientRect() of child element wrt to parent in px and passinig from one component to another , and able to place the child elements on the expected positions , however I want to make child elements responsive wrt to parent element , for that I need top and left positions in % I already tried changing values and unit from px to % from console and was able to get desired responsiveness, however trying to calculate in percentage in the code is not working as expected, it doesn't place the child element on correct position, though responsiveness is achieved.

  minX: viewRect.left - movableClientRect.left + movable.position.x,
  maxX: viewRect.right - movableClientRect.right + movable.position.x,
  minY: viewRect.top - movableClientRect.top + movable.position.y,
  maxY: viewRect.bottom - movableClientRect.bottom + movable.position.y

 this.dataService.setData(this.zonePosition = 
 {
  x : (viewRect.left - movableClientRect.left)/viewRect.left ,
  y: (viewRect.top - movableClientRect.top)/viewRect.top
  }

In the earlier code I was only doing viewRect.left - movableClientRect.left , so values were in pixel , and now I tried dividing by viewRect.left and then * 100 to convert it into % but the percentage value doesn't place the child elements correctly.

Updating with the directives for calculating the position

movable.directive In this I calculate the x and y pos of the child element

interface Position {
  x: number;
  y: number;
}

@Directive({
 selector: '[appMovable]'
})
export class MovableDirective extends DraggableDirective {
@HostBinding('style.transform') get transform(): SafeStyle {
   return this.sanitizer.bypassSecurityTrustStyle(
  `translateX(${this.position.x}%) translateY(${this.position.y}%)`
  );
 }

 @HostBinding('class.movable') movable = true;

 position: Position = {x: 0, y: 0};
 zonePosition = {x:0, y:0, id:""}

 private startPosition: Position;

 @Input('appMovableReset') reset = false;

 constructor(private sanitizer: DomSanitizer, public element: ElementRef, 
  private dataService: DataService) {
  super(element);
  }

 @HostListener('dragStart', ['$event'])
  onDragStart(event: PointerEvent) {
   this.startPosition = {
     x: event.clientX - this.position.x,
     y: event.clientY - this.position.y
   }
 }

 @HostListener('dragMove', ['$event'])
  onDragMove(event: PointerEvent) {
   this.position.x = event.clientX - this.startPosition.x;
   this.position.y = event.clientY - this.startPosition.y;
  }

  @HostListener('dragEnd', ['$event'])
   onDragEnd(event: PointerEvent) {
    if (this.reset) {
     this.position = {x: 0, y: 0};
    }
   }
   }

The directive in which I am calculating the left and top of the child element and assigning it to dataService

  Directive({
   selector: '[appMovableArea]'
  })
  export class MovableAreaDirective implements AfterContentInit {
  @ContentChildren(MovableDirective) movables: QueryList<MovableDirective>;

  private boundaries: Boundaries;
  private subscriptions: Subscription[] = [];

  zonePosition = {x:0, y:0, id:""}

  constructor(private element: ElementRef, private dataService: DataService) 
  {}

  ngAfterContentInit(): void {
   this.movables.changes.subscribe(() => {
   this.subscriptions.forEach(s => s.unsubscribe());

   this.movables.forEach(movable => {
   this.subscriptions.push(movable.dragStart.subscribe(() => 
       this.measureBoundaries(movable)));
   this.subscriptions.push(movable.dragMove.subscribe(() => 
   this.maintainBoundaries(movable)));
   });
   });

   this.movables.notifyOnChanges();
    }

   private measureBoundaries(movable: MovableDirective) {
    const viewRect: ClientRect = 
       this.element.nativeElement.getBoundingClientRect();
    const movableClientRect: ClientRect = 
       movable.element.nativeElement.getBoundingClientRect();

    this.dataService.setData(this.zonePosition= {x : (viewRect.left - 
      movableClientRect.left)/viewRect.left , y: (viewRect.top - 
      movableClientRect.top)/viewRect.top, id: "" })

    this.boundaries = {
      minX: viewRect.left - movableClientRect.left + movable.position.x,
      maxX: viewRect.right - movableClientRect.right + movable.position.x,
      minY: viewRect.top - movableClientRect.top + movable.position.y,
      maxY: viewRect.bottom - movableClientRect.bottom + movable.position.y
    };
   }

    private maintainBoundaries(movable: MovableDirective) {
     movable.position.x = Math.max(this.boundaries.minX, 
      movable.position.x);
    movable.position.x = Math.min(this.boundaries.maxX, movable.position.x);
    movable.position.y = Math.max(this.boundaries.minY, movable.position.y);
    movable.position.y = Math.min(this.boundaries.maxY, movable.position.y);
   }
   }
Enthu
  • 512
  • 2
  • 13
  • 38

1 Answers1

2

If you already have the position in pixels relative to the container element, then all you need is to divide this value in px by the size of the container in pixel and multiply this by 100:

perc_x = px_x / px_width * 100;
perc_y = px_y / px_height * 100;

elem = document.querySelector('.content');
container = document.querySelector('.container');
const mouse = {
  x: null,
  y: null,
  down: false
};
let will_draw = false;

elem.onmousedown = e => {
  mouse.down = true;
};
onmouseup = e => {
  mouse.down = false;
};
document.onmousemove = e => {
  if(!mouse.down) { return; }

  const container_rect = container.getBoundingClientRect();
  // relative to container, in px
  mouse.x = e.clientX - container_rect.left;
  mouse.y = e.clientY - container_rect.top;
  if(!will_draw) {
    requestAnimationFrame(draw);
    will_draw = true;
  }
}

function draw() {
  will_draw = false;
  const { width, height} = container.getBoundingClientRect();
  const perc_x = mouse.x / width * 100;
  const perc_y = mouse.y / height * 100;
  // -5 to center (elem has its width set to 10%)
  elem.style.setProperty('left', (perc_x - 5) + '%');
  // -5 to center (elem has its height set to 10%)
  elem.style.setProperty('top', (perc_y - 5) + '%');
}
.container {
  width: 80vw;
  height: 80vh;
  border: 1px solid;
  position: relative;
}
.content {
  width: 10%;
  height: 10%;
  position: absolute;
  top: 45%;
  left: 45%;
  background: green;
}
<div class="container">
  <div class="content">
  </div>
</div>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • I have also updated my post with the calculation I have applied but I am confused whether the top and left of element I am getting is wrt to its parent or wrt to the viewport, my objective is to get wrt to its parent, please suggest – Enthu Aug 15 '19 at 04:00
  • @Enthu just look at my snippet? I do it all. To get the position relative to the container you just do mouseEvent.clientX - container.getBoundingClientRect().left – Kaiido Aug 15 '19 at 04:18
  • @Enthu, we set top left coords of our element through CSS. The `- 5` is to make our mouse point the center of our element. Since our element is 10×10% width, to make **its center** at say 50% 50%, then we need to set its top and left porperties to 45%. That's what this line does. – Kaiido Aug 15 '19 at 23:13