Hand-written Drag-and-grab Levitating Ball Assembly is a bit long, but it’s very productive

This is the first day of my participation in the More text Challenge. For details, see more text Challenge

preface

There are a variety of UI frameworks in the front-end ecosystem, but there are a lot of UI frameworks that don’t have a floating ball component. It’s not hard to implement, so I decided to write one myself!

Implementation approach

  1. Get screenwidthheightThat is, the range of motion of the floating ball is obtained
  2. Using CSSpositiontheabsoluteAttributes, and collocationleftandtopAttribute to implement element location

The process of dragging and dropping events

Select element > Drag Element > Drag end

Let’s start with our theme

Implement a class that lets users pass in DOM elements that need to be dragged

Define a class and export it. The class is called Drag, and define the necessary properties

export default class Drag{
  / / element
  element: HTMLElement;

  // Screen size
  screenWidth: number;

  screenHeight: number;

  // Element size
  elementWidth: number;

  elementHeight: number;

  isPhone: boolean;

  // The current element coordinates
  elementX: number;

  elementY: number;

  / / element offset
  elementOffsetX: number;

  elementOffsetY: number;

  // Whether it is in the drag state
  moving: boolean;

  / / adsorption
  autoAdsorbent: boolean;

  / / hide
  hideOffset: number;
}
Copy the code

In the Drag class, we create a constructor that declares the parameters we need to pass in. Elements are essential, so our first argument is the DOM element

constructor(element: HTMLElement) {
	// I need to pass in a DOM element, which is the element dragged by the user
 }
Copy the code
  • Initialize some parameters
    • Gets the width and height of the screen
    • Gets the width and height of the element
    • Identify the device and throw one if it is a computer deviceerror
    • The elementpositionProperty is set toabsolute
  constructor(element: HTMLElement) {
    this.element = element;
    this.screenWidth = window.innerWidth || window.outerWidth || 0;
    this.screenHeight = window.innerHeight || window.outerHeight || 0;
    this.elementWidth = this.element.offsetWidth || 0;
    this.elementHeight = this.element.offsetHeight || 0;
    this.isPhone = /(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent);
    this.element.style.position = 'absolute';
    this.elementX = 0;
    this.elementY = 0;
    this.elementOffsetX = 0;
    this.elementOffsetY = 0;
    this.moving = false;
    if (!this.isPhone) {
      console.error('Warning!! The current plug-in version is only compatible with mobile devices'); }}Copy the code
  • To define awatchTouchMethod to add an event to the drag element
    • There’s another thing to note here,touchEventIt is not possible to get the offset value directlySo we took advantage of ittouchObject.pageX / touchObject.pageY - DOMRect.left / DOMRect.topTo get the elementsoffsetvalue
private watchTouch(): void {
    this.element.addEventListener('touchstart'.(event: TouchEvent) = > {
      const rect = (event.target as HTMLElement).getBoundingClientRect();
      // The height of the page to be rolled off
      // Not compatible with IE
      const docScrollTop = document.documentElement.scrollTop;
      this.elementOffsetX = event.targetTouches[0].pageX - rect.left;
      this.elementOffsetY = event.targetTouches[0].pageY - rect.top - docScrollTop;
      this.moving = true;
      this.element.addEventListener('touchmove'.this.move.bind(this), { passive: false });
    });
    window.addEventListener('touchend'.() = > {
      this.moving = false;
      document.removeEventListener('touchmove'.this.move);
    });
  }
Copy the code
  • Define an element position method, passed inxandyTo set theleftandtopvalue
  private setElementPosition(x: number.y: number) :void {
    // Overflow processing
    // Overflow scope
    // If the page is beyond the screen range, calculate the current screen range
    const leftScope = this.moving ? 0 : 0 - this.hideOffset;
    // The maximum value of the current screen right
    const rs = this.screenWidth - this.elementWidth;
    const rightScope = this.moving ? rs : rs + this.hideOffset;
    const bottomScope = this.screenHeight - this.elementHeight;
    if (x <= leftScope && y <= 0) {
      [x, y] = [leftScope, 0];
    } else if (x >= rightScope && y <= 0) {
      [x, y] = [rightScope, 0];
    } else if (x <= leftScope && y >= bottomScope) {
      [x, y] = [leftScope, bottomScope];
    } else if (x >= rightScope && y >= bottomScope) {
      [x, y] = [rightScope, bottomScope];
    } else if (x > rightScope) {
      x = rightScope;
    } else if (y > bottomScope) {
      y = bottomScope;
    } else if (x <= leftScope) {
      x = leftScope;
    } else if (y <= 0) {
      y = 0;
    }
    this.elementX = x;
    this.elementY = y;
    this.element.style.top = `${y}px`;
    this.element.style.left = `${x}px`;
  }
Copy the code
  • To define amoveMethod, which calls the one set abovesetElementPositionmethods
private move(event: TouchEvent): void {
    event.preventDefault();
    if (!this.moving) return;
    this.elementY = (event.touches[0].pageX - this.elementOffsetX);
    this.elementX = (event.touches[0].pageY - this.elementOffsetY);
    const ex = (event.touches[0].pageX - this.elementOffsetX);
    const ey = (event.touches[0].pageY - this.elementOffsetY);
    this.setElementPosition(ex, ey);
  }
Copy the code

At this point our component is ready for simple drag and drop!

But it’s not quite the adsorption that we talked about before

Let’s go ahead and add a snap to the Drag class

  • Adsorption of ideas
    • whentouchendWhen the event is triggered, we need to decide which side is the current element hanging closer to the screen
      • const screenCenterY = Math.round(this.screenWidth / 2);
      • this.elementX < screenCenterY
    • Define an animation function, which is the transition effect of the element from point A to point B.
    • Define the adsorption function switch
  private animate(targetLeft: number.spd: number) :void {
    const timer = setInterval(() = > {
      let step = (targetLeft - this.elementX) / 10;
      // Execute the second processing of the step (> 0, round up, less than 0, round down)
      step = step > 0 ? Math.ceil(step) : Math.floor(step);
      // Animation principle: target position = current position + step
      const x = this.elementX + step;
      this.setElementPosition(x, this.elementY);
      // Check whether the slow animation has stopped
      if (Math.abs(targetLeft - this.elementX) <= Math.abs(step)) {
        // Handle decimal assignment
        const xt = targetLeft;
        this.setElementPosition(xt, this.elementY);
        clearInterval(timer);
      }
    }, spd);
  }
Copy the code
private adsorbent():void {
    // Determine the adsorption direction
    // The center of the screen
    const screenCenterY = Math.round(this.screenWidth / 2);
    // Left Max
    const rightScope = this.screenWidth - this.elementWidth;
    // Determine the adsorption direction according to the center point
    if (this.elementX < screenCenterY) {
      this.animate(0 - (this.hideOffset), 10);
    } else {
      this.animate(rightScope + (this.hideOffset), 10); }}Copy the code

Define an interface as the second argument to Drag:

interfaceOptions { autoAdsorbent? :boolean;
}
Copy the code

Change the previous constructor method argument to:

constructor(element: HTMLElement, dConfig: Options = {})
Copy the code

Add an autoAdsorbent attribute to the Drag class to determine if the user has adsorbed enabled

export default class Drag{
  / / adsorption
  autoAdsorbent: boolean;
  / /...
}
Copy the code

In the watchTouch method, the TouchEnd event is added

 window.addEventListener('touchend'.() = > {
      // ...
      if (this.autoAdsorbent) this.adsorbent();
    });
Copy the code

There’s a little bit of a problem here, what if the user doesn’t pass in dConfig?

We can add a sentence in the Construction method that means if the dConfig parameter, autoDeterminate Bent, does not exist, set it to false

  constructor(element: HTMLElement, dConfig: Options = {}) {
 	 dConfig = {autoAdsorbent: dConfig.autoAdsorbent || false}}Copy the code

Use our Drag class

The first step is to introduce the Drag that we wrote

import Drag from 'Drag';
Copy the code
<div class="root">
    <div class="BDrag"></div>
</div>
Copy the code
.drag{
    width: 50px;
    height: 50px;
    background-color: rgb(238.238.238);
    border-radius: 50%;
    border: 5px solid rgb(170.170.170);
}
Copy the code

BetterGraggbleBall provides a class that is instantiated with a native DOM element as the first parameter

const BDragDom = document.getElementById('BDrag');
const BDrag = new BDrag(BDragDom);
Copy the code

GIT: github.com/QC2168/bett…

You can also install it directly using NPM

npm install better-draggable-ball --save
Copy the code

At the end

If you find this article helpful, please click 👍 and follow.