Change opacity then hide on swipe up (CSS / JS)

Viewed 631

I'm looking to change opacity and then completely hide div on swipe up on certain threshold, like in the video below or in Photoswipe:

https://www.loom.com/share/29741bdadc7846bfbc747d3870815340

Unfortunately most off libraries only allow to register actual event start end, but not the amount of swiped pixels. How would I get the actual swiped distance and connect it to the swipe event?

2 Answers

With a lot of event listeners and computed properties; I made a quick code pen using W3's draggable function, but added the opacity change myself:

// Make the DIV element draggable:
dragElement(document.getElementById("mydiv"));

function dragElement(elmnt) {
  var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  if (document.getElementById(elmnt.id + "header")) {
    // if present, the header is where you move the DIV from:
    document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
  } else {
    // otherwise, move the DIV from anywhere inside the DIV:
    elmnt.onmousedown = dragMouseDown;
  }

  function dragMouseDown(e) {
    e = e || window.event;
    e.preventDefault();
    // get the mouse cursor position at startup:
    pos3 = e.clientX;
    pos4 = e.clientY;
    document.onmouseup = closeDragElement;
    // call a function whenever the cursor moves:
    document.onmousemove = elementDrag;
  }

  function elementDrag(e) {
    e = e || window.event;
    e.preventDefault();
    // calculate the new cursor position:
    pos1 = pos3 - e.clientX;
    pos2 = pos4 - e.clientY;
    pos3 = e.clientX;
    pos4 = e.clientY;
    // set the element's new position:
    elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
    elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
    //change background opacity:
    const background = document.getElementById("background");
    const bgHeight = background.offsetHeight;
    const elmntHeight = elmnt.offsetHeight;
    const adjustedBottom = bgHeight - elmntHeight;
    const percentage = 1 - elmnt.offsetTop / adjustedBottom; 
    console.log(percentage)
    
    background.style.opacity = percentage;
  }

  function closeDragElement() {
    // stop moving when mouse button is released:
    document.onmouseup = null;
    document.onmousemove = null;
  }
}
body {
  margin: 0;
}

#background {
  background: black;
  width: 100vw;
  height: 100vh; 
  position: absolute;
}

#mydiv {
  position: absolute;
  z-index: 9;
  background-color: #f1f1f1;
  border: 1px solid #d3d3d3;
  text-align: center;
}

#mydivheader {
  padding: 10px;
  cursor: move;
  z-index: 10;
  background-color: #2196F3;
  color: #fff;
}
<div id="background"></div>
<!-- Draggable DIV -->
<div id="mydiv">
  <!-- Include a header DIV with the same name as the draggable DIV, followed by "header" -->
  <div id="mydivheader">Click here to move</div>
  <p>Move</p>
  <p>this</p>
  <p>DIV</p>
  </div>
</div>

Far from perfect, but hopefully demonstrates an idea to expand on.

Note: You can apply the animations used in this example on other elements like an overlay instead. The technique is the same.

Here is some code to move up an element, fade it out and remove it from display. Note that I only implemented the PointerEvent-api. You should also implement a fallback.

A summary about what is going on:

  1. Detect a pointerdown on the element and allow the pointer to be used outside the element with setPointerCapture().
  2. Detect a pointermove on the element. If the mouse/touch is moved up, also move up the element. ( I also restricted movement to the left, right, bottom, but you don't have to)
  3. Detect a pointerup. After releasePointerCapture() the pointer will once more only be available in the default element and not outside it. Depending on the amount the element has moved up, the element is returned to its original position or animated out.

class SwipeOutBehaviour {
  constructor(element) {
    this.element = element;
    this.dy = null;
    this.initial_y = null;
    this.animation_frame_state = 'completed';

    if( window.PointerEvent ) { 
      this.element.addEventListener('pointerdown', this.start_drag.bind(this), true);     
      this.element.addEventListener('pointermove', this.drag.bind(this), true);
      this.element.addEventListener('pointerup', this.drag_end.bind(this), true);
    } else {
    //should use composition instead if you re serious, for this example I only implemented PointerEvent some browsers will use Tpuchevent and MouseEvent
    }
  }

  start_drag( event ){
    event.preventDefault();
      // only respond to a single touch
      if( event.touches && event.touches.length > 1 ) return;
      // allow pointerevents outside the target
      event.target.setPointerCapture(event.pointerId);
      // set initial pos
      this.initial_y = ( event.targetTouches ) ? event.targetTouches[0].clientY : event.clientY;
  }
  drag( event ){
    event.preventDefault();
    
    if( this.initial_y === null ) return;
    if( this.animation_frame_state === 'pending' ) return;

    this.dy = ( event.targetTouches ) ? Math.floor( event.targetTouches[0].clientY - this.initial_y ) : Math.floor( event.clientY - this.initial_y );
    
    if( this.dy > 0 ) return;
    this.animation_frame_state = 'pending'
    window.requestAnimationFrame( () => {
      this.element.style.transform = `translateY(${this.dy}px)`
      this.animation_frame_state = 'completed';
    });
  }
  drag_end(event) {
    event.preventDefault();
        
    if(event.touches && event.touches.length > 0) return;
    
    event.target.releasePointerCapture(event.pointerId);
    if( this.dy < -100 ) {
      window.requestAnimationFrame( () => {
        this.element.style.transition = 'opacity 500ms, translateY 200ms';
        this.element.style.transform = `translateY(-175px)`;
        this.element.style.opacity = `0`;
        this.animation_frame_state = 'completed';
        window.setTimeout( () => {
          // set display to none, you could remove it from the DOM instead
          this.element.style.display = 'none';
        }, 500)
      });
    } else {
      window.requestAnimationFrame( () => {
        this.element.style.transform = `translateY(0px)`
        this.animation_frame_state = 'completed';
      });  
    }
    this.initial_y = null;
  }
}

let element = document.getElementById('container');
new SwipeOutBehaviour( element );
  #container {
    margin: auto;
    width: 150px;
    height: 150px;
    border: 1px solid black;
  }
  #box-of-doom {
    margin: auto;
    width: 200px;
    height: 200px;
    border: 1px solid red;
    background: orange;
  }
  p {
    text-align: center;
  }
<p>Drag the item in the box of doom<p>
<div id='box-of-doom'>
  <p>The box of doom<p>
</div>
<div id='container'>
  <img alt='a placeholder' src='https://via.placeholder.com/150' />
</div>

Note: This answer is inspired by this documentation/article from Google about touch events, so you may want to read more there.

Related