简体   繁体   中英

After a user swipes (touchstart, 1 or more touchmove, touchend), how calculate how far to keep moving an element?

I want to move an element based on the velocity of a user's swipe. I have the easing function to make it start fast and get slower over time, but the one variable I need to calculate based on their swipe is totalTime (or totalSteps ).

How would I calculate this?

What I know:

  1. The time they started the swipe
  2. The time and distance of each touchmove
  3. The time the swipe ended ( touchend )

From that I need to calculate how far to move them (the easing function will handle the distance of each individual step). How do i calculate this?

Easing function:

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

The change is what i need to calculate.

To make this works you need to cycle this like my example:

At first you need to get coordinates of first and last touches inside event and store it somewhere outside touch events:

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

After getting finish coordinates execute this inside touchend event:

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);
                }
        })();

UPDATED

To make animation works more efficient you need to use touchmove event instead of touchend firing it within a delay.

To just get the time between dragstart and dragend :

 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> 

Obviously you will need to attach other events as well touchstart , touchend , etc.

This approach in action

If I understood your question correctly then this should help. It's pretty much taking touchstart and touchend as reference points and working with them.

 /** * * 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> 

I am not the creator of the code, if you need any more samples of performant front end you can find them here and follow aerotwist

Edit

So now it uses the velocity of the actual drag, you still may need some tweaking to get the right feeling, I've added a comment in update function there.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM