Search code examples
javascriptjqueryjquery-animateevent-listener

Issues with swipe and/or animate in JS


What it is: I am having an issue with my swipeable form. It uses a vanilla JS method to achieve swipe recognition and .animate to cycle through each part of the form.

My problem: When too many swipes are performed in succession or too many next buttons are clicked in succession, the .animate seems to fail and they start to pile up in the center of the visible area.

Things I've tried: I've tried stopPropagation() and setTimeout(), but neither have helped. I've also tried removing the listeners at the start of their function and adding them back at the end of the nextPrev() function, which also didn't correct the issue.

I'm not sure where exactly it is going wrong and therefore don't know how to troubleshoot this. I've tried different things at various stages and nothing seems to correct this issue. I am looking for a way to prevent this from happening.

I apologize for not presenting an MCVE, I left everything in intentionally so someone could experience cycling through the form and reproduce the issue.

The code:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script
  src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js">
  </script>
    <style>
      body {
        align-items: center;
        width: 100%;
        font-size: 2em;
        }
      #form {
        display: flex;
        margin: auto;
        align-items: center;
        text-align: center;
        width: 750px;
        height: 750px;
        border: 1px solid black;
        position: relative;
        overflow: hidden;
        }
      #btns {
        margin: auto;
        text-align: center;
        width: 250px;
        height: 50px;
        }
      #back {
        float: left;
        }
      #next {
        float: right;
        }
      table {
        margin: auto;
        }
      .part:not(:first-child) {
        display: inline-block;
        position: absolute;
        right: 0px;
        left: 5000px;
        }
      .part:first-child {
        display: inline-block;
        position: absolute;
        right: 0px;
        left: 0px;
        }
      input[type=text],input[type=number],input[type=date],input[type=time], select {
        width: 500px;
        padding: 30px 20px;
        margin: 8px 0;
        border: 1px solid Black;
        border-radius: 10px;
        box-sizing: border-box;
        font-size: 1em;
        color: Black;
        }
    input[type=radio] {
        width: 100px;
        height: 100px;
        }
    input[type=checkbox] {
        width: 40px;
        height: 40px;
        }
    </style>
  </head>
  <body>
    <div id="form">
    <div id="0" class="part"><h3>Driver Information</h3></div>
    <div id="1" class="part">Company:<br><input type="text" name="carrier"></div>
    <div id="2" class="part">Station:<br><table>
    <tr><td>PDX:</td><td><input type="radio" id="pdx" name="address"></td></tr>
    <tr><td>EUG:</td><td><input type="radio" id="pdx" name="address"></td></tr>
    <tr><td>SEA:</td><td><input type="radio" id="pdx" name="address"></td></tr>
    <tr><td>SFO:</td><td><input type="radio" id="pdx" name="address"></td></tr></table></div>
    <div id="3" class="part">Name:<br><input type="text" name="name" placeholder="Your Full Name"></div>
    <div id="4" class="part">Employee Number:<br><input type="number" name="employeeNumber" placeholder="Your Employee Number"></div>
    <div id="5" class="part">Date:<br><input type="date" id="date" name="date"></div>
    <div id="6" class="part">Time:<br><input type="time" id="time" name="time"></div>
    <div id="7" class="part"><h3>Vehicle<br>Information</h3></div>
    <div id="8" class="part">Tractor/Truck Number:<br><input type="number" id="tractorTruckNumber" name="tractorTruckNumber"></div>
    <div id="9" class="part">Odometer:<br><input type="number" id="odometer" name="odometer"></div>    
    <div id="10" class="part"><h4><p>Vehicle Inspection<br><small></small></p></h4></div>
    <div id="11" class="part">
    <table>
    <tr><td>Air Compressor:</td><td><input type="checkbox" class="truck" id="aircompressor"></td></tr>
    <tr><td>Air Lines:</td><td><input type="checkbox" class="truck" id="airlines"></td></tr>
    <tr><td>Battery:</td><td><input type="checkbox" class="truck" id="battery"></td></tr>
    <tr><td>Belts and Hoses:</td><td><input type="checkbox" class="truck" id="beltsandhoses"></td></tr>
    <tr><td>Body:</td><td><input type="checkbox" class="truck" id="body"></td></tr>
    </table>
    </div>
    <div id="12" class="part">
    <table>
    <tr><td>Brake Accessories:</td><td><input type="checkbox" class="truck" id="brakeaccessories"></td></tr>
    <tr><td>Brakes, Parking:</td><td><input type="checkbox" class="truck" id="brakesparking"></td></tr>
    <tr><td>Brakes, Service:</td><td><input type="checkbox" class="truck" id="brakesservice"></td></tr>
    <tr><td>Clutch:</td><td><input type="checkbox" id="clutch"></td></tr>
    <tr><td>Coupling Devices:</td><td><input type="checkbox" class="truck" id="couplingdevices"></td></tr>
    </table>
    </div>
    <div id="13" class="part">
    <table>
    <tr><td>Defroster/Heater:</td><td><input type="checkbox" class="truck" id="defrosterheater"></td></tr>
    <tr><td>Drive Line:</td><td><input type="checkbox" class="truck" id="driveline"></td></tr>
    <tr><td>Engine:</td><td><input type="checkbox" class="truck" id="engine"></td></tr>
    <tr><td>Exhaust:</td><td><input type="checkbox" class="truck" id="exhaust"></td></tr>
    <tr><td>Fifth Wheel:</td><td><input type="checkbox" class="truck" id="fifthwheel"></td></tr>
    </table>
    </div>
    <div id="14" class="part">
    <table>
    <tr><td>Fluid Levels:</td><td><input type="checkbox" class="truck" id="fluidlevels"></td></tr>
    <tr><td>Frame and Assembly:</td><td><input type="checkbox" class="truck" id="frameandassembly"></td></tr>
    <tr><td>Front Axle:</td><td><input type="checkbox" class="truck" id="frontaxle"></td></tr>
    <tr><td>Fuel Tanks:</td><td><input type="checkbox" class="truck" id="fueltanks"></td></tr>
    <tr><td>Horn:</td><td><input type="checkbox" class="truck" id="horn"></td></tr>
    </table>
    </div>
    <div id="15" class="part">
    <table>
    <tr><td>Lights:</td><td><input type="checkbox" class="truck" id="lights"></td></tr>
    </table>
    <small><center><p>Head/Stop, Tail/Dash, Turn Indicators, Clearance/Marker</p></center></small>
    </div>
    <div id="16" class="part">
    <table>
    <tr><td>Mirrors:</td><td><input type="checkbox" class="truck" id="mirrors"></td></tr>
    <tr><td>Muffler:</td><td><input type="checkbox" class="truck" id="muffler"></td></tr>
    <tr><td>Oil Pressure:</td><td><input type="checkbox" class="truck" id="oilpressure"></td></tr>
    <tr><td>Radiator:</td><td><input type="checkbox" class="truck" id="radiator"></td></tr>
    <tr><td>Read End:</td><td><input type="checkbox" class="truck" id="rearend"></td></tr>
    <tr><td>Reflectors:</td><td><input type="checkbox" class="truck" id="reflectors"></td></tr> 
    </table>
    </div>
    <div id="17" class="part">
    <table>
    <tr><td>Safety Equipment:</td><td><input type="checkbox" class="truck" id="safetyequipment"></td></tr>
    </table>
    <small><center><p>Fire Extinguisher, Flags/Flares/Fusees, Reflective Triangles, Spare Bulbs/Fuses, Spare Seal Beam</p></center></small>
    </div>
    <div id="18" class="part">
    <table>
    <tr><td>Starter:</td><td><input type="checkbox" class="truck" id="starter"></td></tr>
    <tr><td>Steering:</td><td><input type="checkbox" class="truck" id="steering"></td></tr>
    <tr><td>Suspension System:</td><td><input type="checkbox" class="truck" id="suspensionsystem"></td></tr>
    <tr><td>Tire Chains:</td><td><input type="checkbox" class="truck" id="tirechains"></td></tr>
    <tr><td>Tires:</td><td><input type="checkbox" class="truck" id="tires"></td></tr> 
    <tr><td>Transmission:</td><td><input type="checkbox" class="truck" id="transmission"></td></tr>
    </table>
    </div>
    <div id="19" class="part">
    <table>
    <tr><td>Trip Recorder:</td><td><input type="checkbox" class="truck" id="triprecorder"></td></tr>
    <tr><td>Wheels and Rims</td><td><input type="checkbox" class="truck" id="wheelsandrims"></td></tr>
    <tr><td>Windows</td><td><input type="checkbox" class="truck" id="windows"></td></tr>
    <tr><td>Windshield Wipers:</td><td><input type="checkbox" class="truck" id="windshieldwipers"></td></tr>
    <tr><td>Other:</td><td><input type="checkbox" class="truck" id="other"></td></tr> 
    </table>
    </div>
    <div id="20" class="part">Remarks:<br><input type="text" id="truckremarks"></div>
    <div id="21" class="part"><h3>Trailer Inspection</h3></div>
    <div id="22" class="part">Trailer Number:<br><input type="number" id="trailernumber"><br><h6>(if no trailer, click next)</h6></div>

    </div>
    <div id="btns"><input type="button" id="back" onclick="nextPrev(-1)" value="<< Back"><input type="button" id="next" onclick="nextPrev(1);" value="Next >>"></div>
  </body>
  <script>
  var part = $('.part')
  var currentPart = 0;
  checkButtons();
  var isSwiping = false;

  function startSwipe(){
    if (isSwiping){
      return false;
      }
      else {
      isSwiping = true;
      $('#back, #next').attr('disabled',true);
      return true;
      }
    }
  function endSwipe(){
    $('#back, #next').removeAttr('disabled');
    isSwiping = false;
    }

  function nextPrev(n){
    if (startSwipe()){//this doesn't work when swiping with startSwipe(), but buttons still work
      if (n === 1){
        if (currentPart !== part.length-1){
          $('#'+currentPart).filter(':not(:animated)').animate({'left':'-5000px'},500,function(){
            $(this).css({'left':'-5000px'});//set current left
            });//move current left
          $('#'+(currentPart+n)).delay(200).filter(':not(:animated)').animate({'left':'0'},500,function(){
            $(this).css({'left':'0'});//set next centered
            currentPart = currentPart + n;
            checkButtons();
            endSwipe();
            });//move next right   
          }
        }
      if (n === -1){
        if (currentPart !==0){
          $('#'+currentPart).filter(':not(:animated)').animate({'left':'5000px'},500,function(){
            $('#'+currentPart).css({'left':'5000px'});//set current right
            });//move current left
          $('#'+(currentPart+n)).delay(200).filter(':not(:animated)').animate({'left':'0'},500,function(){
            $('#'+(currentPart+n)).css({'left':'0'});//set next centered
            currentPart = currentPart + n;
            checkButtons();
            endSwipe();
            });//move next right
          }
        }
      }  
    }

  function checkButtons(){
    if (currentPart == 0){
      $('#back').css({'display':'none'});
      }
    else if (currentPart == part.length-1){
      $('#next').css({'display':'none'});
      }
    else {
      $('#back').css({'display':'block'});
      $('#next').css({'display':'block'});
      }
    }

document.getElementById('form').addEventListener('touchstart', handleTouchStart, false);        
document.getElementById('form').addEventListener('touchmove', handleTouchMove, false);
document.getElementById('form').addEventListener('touchend', handleTouchEnd, false);

var xDown = null;                                                        
var yDown = null;

function getTouches(evt) {
  return evt.touches ||             // browser API
         evt.originalEvent.touches; // jQuery
}                                                     

function handleTouchStart(evt) {
  if (startSwipe()){
    const firstTouch = getTouches(evt)[0];                                      
    xDown = firstTouch.clientX;                                      
    yDown = firstTouch.clientY;  
  }
};                                                

function handleTouchMove(evt) {
  if (isSwiping){
    if ( ! xDown || ! yDown ) {
        return;
    }
    var xUp = evt.touches[0].clientX;                                    
    var yUp = evt.touches[0].clientY;

    var xDiff = xDown - xUp;
    var yDiff = yDown - yUp;

    if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
        if ( xDiff > 0 ) {
            nextPrev(1);
        } else {
            nextPrev(-1);
        }                       
    }

    /* reset values */
    xDown = null;
    yDown = null;
  }
};

function handleTouchEnd(evt) {
  endSwipe();
  };
  </script>
</html>

update: ariel's solution is a good one, though it did not fix my problem. This sort of flagging is important, and my code should've already been equipped with it. I am still experiencing issues, though I found something promising here: https://css-tricks.com/full-jquery-animations/ Though it does not fix my problem completely, it is getting very close to a solution and I will update when I have one.


Solution

  • You need to use a flag to prevent the event firing when the animation is happening:

    var isSwiping = false;
    
    function startSwipe() {
      if (isSwiping) {
        return false;
      } else {
        isSwiping = true;
        $("button#back, button#next").attr("disabled", true);
        return true;
      }
    }
    
    function endSwipe() {
      $("button#back, button#next").removeAttr("disabled");
      isSwiping = false;
    }
    
    function nextPrev(n) {
      if (startSwipe()) {
        ...
          // Inside animate callback:
          endSwipe();
        ...
    }
    
    ...
    
    document.getElementById('form').addEventListener('touchstart', handleTouchStart, false);        
    document.getElementById('form').addEventListener('touchmove', handleTouchMove, false);
    document.getElementById('form').addEventListener('touchend', handleTouchEnd, false);
    
    ...
    
    function handleTouchStart(evt) {
      if (startSwipe()) {
        ...                                      
      }
    };                                                
    
    function handleTouchMove(evt) {
      if (isSwiping) {
        ...
      }
    };
    
    function handleTouchEnd(evt) {
      endSwipe();
    };