用户轻扫(触摸开始、1 次或多次触摸移动、触摸结束)后,如何计算元素保持移动的距离



我想根据用户的滑动速度移动一个元素。我有缓动功能,可以让它随着时间的推移快速启动并变慢,但我需要根据他们的滑动计算的一个变量是 totalTime(或totalSteps)。

我将如何计算?

我所知道的:

  1. 他们开始滑动的时间
  2. 每个touchmove的时间和距离
  3. 滑动结束的时间 ( touchend

由此,我需要计算将它们移动多远(缓动函数将处理每个单独步骤的距离)。我该如何计算?

缓动功能:

function easeOutCubic(currTime, beginningValue, change, duration)
{
    return change * ( ( currTime = currTime / duration - 1 ) * currTime * currTime + 1 ) + beginningValue;
}

change是我需要计算的。

要使其正常工作,您需要像我的示例一样循环:

首先,您需要在事件中获取第一次和最后一次触摸的坐标,并将其存储在触摸事件之外的某个位置:

let startCoords = { x: event.touches[0].pageX, y : event.touches[0].pageY } 
let endCoords = { /* same way */ } 

获得完成坐标后,在事件touchend执行以下命令:

const animationTime = 0.5; // Animation time in seconds
const frameRate = 60;
var currentIteration = 0;
var iterationsCount = Math.round(frameRate * animationTime);

(function animate() {
    var x = easeOutCubic(currentIteration, startCoords.x, endCoords.x, iterationsCount);
    var y = easeOutCubic(currentIteration, startCoords.y, endCoords.y, iterationsCount);
                //here you set new x,y to your target element like this
                element.style.top = y +'px';
                element.style.left = x + 'px';

                currentIteration++;
                if (currentIteration < iterationsCount) {
                        requestAnimationFrame(animate);
                }
        })();

更新

为了使动画工作更有效率,您需要使用touchmove事件,而不是touchend延迟内触发它。

要获取 dragstartdragend 之间的时间:

var el = document.getElementById("foo");
var startTime = 0
var timeDelta = 0;
el.addEventListener('dragstart', function(evt){
  startTime = Date.now()/1000;
  });
el.addEventListener('dragend', function(evt){
  var endTime = Date.now()/1000;
  timeDelta = endTime - startTime;
  console.log(timeDelta);
  });
#foo {
  height: 100px;
  width: 100px;
  background: red;
  }
<div id="foo" draggable="true">
<div>

显然,您还需要附加其他事件以及touchstarttouchend等。

这种方法在行动

如果我正确理解了您的问题,那么这应该会有所帮助。它几乎是以touchstarttouchend为参考点并与之合作。

/**
 *
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';
class Cards {
  constructor () {
    this.cards = Array.from(document.querySelectorAll('.card'));
    this.onStart = this.onStart.bind(this);
    this.onMove = this.onMove.bind(this);
    this.onEnd = this.onEnd.bind(this);
    this.update = this.update.bind(this);
    this.targetBCR = null;
    this.target = null;
    this.startX = 0;
    this.startTime = 0;
    this.endTime = 0;
    this.currentX = 0;
    this.screenX = 0;
    this.targetX = 0;
    this.lastVelocity = 0;
    this.draggingCard = false;
    this.addEventListeners();
    requestAnimationFrame(this.update);
  }
  addEventListeners () {
    document.addEventListener('touchstart', this.onStart);
    document.addEventListener('touchmove', this.onMove);
    document.addEventListener('touchend', this.onEnd);
    document.addEventListener('mousedown', this.onStart);
    document.addEventListener('mousemove', this.onMove);
    document.addEventListener('mouseup', this.onEnd);
  }
  onStart (evt) {
    if (this.target)
      return;
    if (!evt.target.classList.contains('card'))
      return;
    this.target = evt.target;
    this.targetBCR = this.target.getBoundingClientRect();
    this.startX = evt.pageX || evt.touches[0].pageX;
    this.startTime = Date.now()/1000;
    this.currentX = this.startX;
    this.draggingCard = true;
    this.target.style.willChange = 'transform';
    evt.preventDefault();
  }
  onMove (evt) {
    if (!this.target)
      return;
    this.currentX = evt.pageX || evt.touches[0].pageX;
  }
  onEnd (evt) {
    if (!this.target)
      return;
    this.targetX = 0;
    this.endTime = Date.now() /1000;
    let screenX = this.currentX - this.startX;
    const threshold = this.targetBCR.width * 0.35;
    if (Math.abs(screenX) > threshold) {
      this.targetX = (screenX > 0) ?
           this.targetBCR.width :
          -this.targetBCR.width;
    }
    this.draggingCard = false;
    
    // calculate velocity
    this.lastVelocity = (evt.pageX - this.startX )/ (this.endTime - this.startTime);
    console.log(this.lastVelocity);
  }
  update () {
    requestAnimationFrame(this.update);
    if (!this.target)
      return;
    if (this.draggingCard) {
      this.screenX = this.currentX - this.startX;
    } else {
      this.screenX += this.lastVelocity / 20; // change the number 20 to change the velocity applied to animation
    }
   
    const normalizedDragDistance =
        (Math.abs(this.screenX) / this.targetBCR.width);
    const opacity = 1 - Math.pow(normalizedDragDistance, 3);
    this.target.style.transform = `translateX(${this.screenX}px)`;
    this.target.style.opacity = opacity;
    // User has finished dragging.
    if (this.draggingCard)
      return;
    const isNearlyAtStart = (Math.abs(this.screenX) < 0.1);
    const isNearlyInvisible = (opacity < 0.01);
    // If the card is nearly gone.
    if (isNearlyInvisible) {
      // Bail if there's no target or it's not attached to a parent anymore.
      if (!this.target || !this.target.parentNode)
        return;
      this.target.parentNode.removeChild(this.target);
      const targetIndex = this.cards.indexOf(this.target);
      this.cards.splice(targetIndex, 1);
      // Slide all the other cards.
      this.animateOtherCardsIntoPosition(targetIndex);
    } else if (isNearlyAtStart) {
      this.resetTarget();
    }
  }
  animateOtherCardsIntoPosition (startIndex) {
    // If removed card was the last one, there is nothing to animate.
    // Remove the target.
    if (startIndex === this.cards.length) {
      this.resetTarget();
      return;
    }
    const onAnimationComplete = evt => {
      const card = evt.target;
      card.removeEventListener('transitionend', onAnimationComplete);
      card.style.transition = '';
      card.style.transform = '';
      this.resetTarget();
    };
    // Set up all the card animations.
    for (let i = startIndex; i < this.cards.length; i++) {
      const card = this.cards[i];
      // Move the card down then slide it up.
      card.style.transform = `translateY(${this.targetBCR.height + 20}px)`;
      card.addEventListener('transitionend', onAnimationComplete);
    }
    // Now init them.
    requestAnimationFrame(_ => {
      for (let i = startIndex; i < this.cards.length; i++) {
        const card = this.cards[i];
        // Move the card down then slide it up, with delay according to "distance"
        card.style.transition = `transform 150ms cubic-bezier(0,0,0.31,1) ${i*50}ms`;
        card.style.transform = '';
      }
    });
  }
  resetTarget () {
    if (!this.target)
      return;
    this.target.style.willChange = 'initial';
    this.target.style.transform = 'none';
    this.target = null;
  }
}
window.addEventListener('load', () => new Cards());
/**
 *
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
html, body {
  margin: 0;
  padding: 0;
  background: #FAFAFA;
  font-family: Arial;
  font-size: 30px;
  color: #333;
}
* {
  box-sizing: border-box;
}
.card-container {
  width: 100%;
  max-width: 450px;
  padding: 16px;
  margin: 0 auto;
}
.card {
  background: #FFF;
  border-radius: 3px;
  box-shadow: 0 3px 4px rgba(0,0,0,0.3);
  margin: 20px 0;
  height: 120px;
  display: flex;
  align-items: center;
  justify-content: space-around;
  cursor: pointer;
}
<!--
Copyright 2016 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="card-container">
    <div class="card">Das Surma</div>
    <div class="card">Aerotwist</div>
    <div class="card">Kinlanimus Maximus</div>
    <div class="card">Addyoooooooooo</div>
    <div class="card">Gaunty McGaunty Gaunt</div>
    <div class="card">Jack Archibungle</div>
    <div class="card">Sam "The Dutts" Dutton</div>
  </div>

我不是代码的创建者,如果您需要更多高性能前端的示例,您可以在此处找到它们并关注 aerotwist

编辑

所以现在它使用实际阻力的速度,您可能仍然需要一些调整才能获得正确的感觉,我在那里的更新功能中添加了注释。

最新更新