Search code examples
javascriptswipetouch-event

Exclude scroll from touch events in javascript


I am trying to make a swipeout list in javascript, something like Gmail app on android has. I am using events like touchstart, touchmove and touchend. Everything works fine when I swipe a list item left or right, but when I try to scroll down through the list it moves the list item that my finger is on (because I don't drag my finger completely vertical when I scroll). Is there an easy way to solve this by differentiating swipe event from scroll event or something like that?

const SwipeOutList = function(listId, items) {
  const listElement = document.getElementById(listId);
  let touchStart = null;
  let movedTo = null;

  const findContentParent = function(element) {
    if(!element.parentNode) {
      return null;
    }
    if(element.className === 'content') {
      return element;
    } else {
      return findContentParent(element.parentNode);
    }
  };

  const fja = function(e) {
    movedTo = e.changedTouches[0].clientX;
    let contentElement;

    if(e.target.className === 'content') {
      contentElement = e.target;
    } else {
      contentElement = findContentParent(e.target);
    }

    console.log(contentElement);

    if(touchStart > movedTo) {
      contentElement.style.transform = `translate3d(${-Math.abs(touchStart - movedTo)}px, 0px, 0px)`;
    } else {
      contentElement.style.transform = `translate3d(${Math.abs(touchStart - movedTo)}px, 0px, 0px)`;
    }
  };

  const handleTouchEnd = function(e) {
    let contentElement = e.target;
    if(contentElement.className !== 'content') {
      contentElement = findContentParent(contentElement);
    }
    let a = parseInt(contentElement.style.transform.split('(')[1]);
    if(Math.abs(a) > contentElement.offsetWidth/2) {
      swipeOut(a, contentElement);
    } else {
      snapBack(a, contentElement);
    }
  };

  const snapBack = function(currentTransform, element) {
    let interval = setInterval(() => {
      if(currentTransform > 0) {
        element.style.transform = `translate3d(${currentTransform - 5}px, 0px, 0px)`;
        currentTransform -= 5;
      } else {
        element.style.transform = `translate3d(${currentTransform + 5}px, 0px, 0px)`;
        currentTransform += 5;
      }

      if(Math.abs(currentTransform) <= 5) {
        element.style.transform = `translate3d(0px, 0px, 0px)`;
        clearInterval(interval);
      }

      if(currentTransform === 0) {
        clearInterval(interval);
      }
    }, 10);
  };

  const composeListItem = function(content) {
    let listItem = document.createElement('div');
    listItem.className = 'swipe-out-list-item';
    listItem.style.cssText = 'position: relative; height: 100px; transition: height .5s;';
    let contentElement = document.createElement('div');
    contentElement.className = 'content';
    contentElement.style.cssText = 'box-sizing: border-box; background-color: white; position:absolute; top: 0; left: 0; border-bottom: solid 1px blue; min-height: 2em; width: 100%; height: 100%;';
    if(content.nodeType === Node.ELEMENT_NODE) {
      contentElement.appendChild(content);
    } else {
      contentElement.innerText = content;
    }

    listItem.appendChild(contentElement);
    let backgroundElement = document.createElement('div');
    backgroundElement.className = 'background';
    backgroundElement.style.cssText = 'display: flex; align-items: center; justify-content: space-between; transition: all 0.5s; z-index: -1; background-color: red; position: absolute; top: 0; left: 0; width: 100%; height: 100%;';
    backgroundElement.innerHTML = `
      <div style="transition: all .5s;">trash</div>
      <div style="transition: all .5s;">trash</div>
    `;
    listItem.appendChild(backgroundElement);
    return listItem;
  };

  const swipeOut = function(currentTransform, element) {
    let interval = setInterval(() => {
      if(currentTransform > 0) {
        element.style.transform = `translate3d(${currentTransform + 5}px, 0px, 0px)`;
        currentTransform += 5;
      } else {
        element.style.transform = `translate3d(${currentTransform - 5}px, 0px, 0px)`;
        currentTransform -= 5;
      }

      if(Math.abs(currentTransform) >= element.offsetWidth + 20) {
        const parent = element.parentNode;
        parent.removeChild(element);
        parent.style.height = '0px';
        parent.children[0].children[0].style.opacity = 0;
        parent.children[0].children[1].style.opacity = 0;
        setTimeout(() => {
          parent.parentNode.removeChild(parent);
        }, 500);
        clearInterval(interval);
      }
    }, 5);
  };

  for (let i = 0; i < items.length; i++) {
    listElement.appendChild(composeListItem(items[i]));

  }

  let array2 = document.querySelectorAll('.content');

  for(let i = 0; i < array2.length; i++) {
    array2[i].addEventListener('touchstart', function(e) {
      touchStart = e.touches[0].clientX;
    })
    array2[i].addEventListener('touchmove', fja);
    array2[i].addEventListener('touchend', handleTouchEnd);
  }

};

Solution

  • So first of all... just straight guessing because I never tried something like that.

    Well, let's go please have a look at my included code =)

    // ------------ // 
    // DOM ELEMENTS //
    // ------------ // 
    const div_moveArea = document.getElementById("moveArea");
    const label_moveAxis = document.getElementById("moveAxis");
    
    // ------- // 
    // GLOBALS //
    // ------- //
    var lastMouseEvent = null;
    
    // --------------- // 
    // EVENT LISTENERS //
    // --------------- // 
    
    div_moveArea.addEventListener("mousemove", event => {
        if(!lastMouseEvent) {
            lastMouseEvent = event;
            return;
        }
        // Get the difference
        let difX = lastMouseEvent.clientX - event.clientX;
        let difY = lastMouseEvent.clientY - event.clientY;
        // Normalize them to a positive value
        if(difX < 0) difX *= -1;
        if(difY < 0) difY *= -1;
        // Get the direction 
        if(difX > difY) label_moveAxis.innerText = "Axis: Horizontal";
        else if(difX < difY) label_moveAxis.innerText = "Axis: Vertical";
        
        else {
            // Should be called if both are the same ... use prefered axis here
        }
        lastMouseEvent = event;
    });
    #moveArea {
        border: 1px solid gray;
        display: block;
        height: 150px;
        width: 300px;
    }
    <!DOCTYPE html>
    <html lang="en">
    <head>
    </head>
    <body>
        <div id="moveArea">Move mouse over this</div>
        <label id="moveAxis">Axis: NONE</label>
    </body>
    </html>

    NOTE: You may want to have a count that changes axis only if the same axis was returned a few times.

    Have a nice day, Elias :)