I'm using touch events to handle swiping a table containing roughly 350 rows. The number of rows is variable as they are pulled from a Google Spreadsheet. The table auto-scrolls continuously, which means that when a rows scrolls off at the top or bottom, I have to append or prepend it again as the case may be.
The user can touch the screen to swipe the table up or down (note that I'm not talking about mobile devices here, but rather a touch-screen monitor). I've provided an implementation for the touchmove event when swiping down, though swiping up would be similar:
function handleMove(event) {
var touch, newY, delta,
$firstItem = $(".item:first"),
$lastItem = $(".item:last");
if (!isDown) {
return;
}
if (event.originalEvent.targetTouches.length == 1) {
touch = event.originalEvent.targetTouches[0];
newY = touch.clientY;
}
delta = lastY - newY;
if (delta < 0) { //Swiping down
//Move last row from the end of the table to the beginning if first row is fully visible.
if (($firstItem.position().top + $firstItem.outerHeight(true)) >= $page.position().top) {
$page.css("margin-top", parseInt($page.css("margin-top")) - $lastItem.outerHeight(true) + "px").prepend($lastItem);
}
$page.css("margin-top", parseInt($page.css("margin-top")) - delta + "px");
}
lastY = newY;
}
The problem is that the more rows that are in the table, the more sluggish the code becomes for moving the row from the bottom to the top and adjusting the margin, causing a jittery swipe.
I'm guessing this is an expensive operation to perform. I read up on browser reflow, but I didn't see many optimizations that I could make to my code. Even tried introducing a DocumentFragment to no avail.
Any ideas on things I can try? Or am I going to have to live with this?
Thx.
Related
I'm putting together a custom UI control in pure JS which simulates a schedule of events. One event is represented by a draggable div.
Here are the most relevant JS functions:
function onDrag(event)
{
console.log("**** DRAG ****");
if (dragging)
{
console.log("dragging = " + dragging);
//event.preventDefault();
currentY = event.clientY - initialY;
// Check whether we need to scroll up or down
//handleScroll();
// Limit dragging to size of background container
if(currentY < 0)
{
currentY = 0;
}
else if(currentY > background_height - draggable.offsetHeight)
{
currentY = background_height - draggable.offsetHeight;
}
else
{
currentY = currentY;
}
yOffset = currentY;
moveIt(currentY);
}
}
function moveIt(yPos)
{
console.log("moveIt : yPos = " + yPos);
draggable.style.transform = "translate3d( 0px, " + yPos + "px, 0)";
}
JSFiddle here
Everything basically works, but not surprisingly, it doesn't work as cleanly as I had hoped.
When dragging to the bottom of the parent container, the container should auto scroll. In practice, the draggable div follows the mouse until I drag below the container, at which point, the draggable bounces back about 300px upwards and plants itself towards the middle of the container (often partly or completely out of the viewport).
What I want is for the draggable to just park itself at the edge of the parent container, even if I try to drag it 1000px away. If I mouse very slowly, the container scrolls properly and the draggable stops on the edge, but if I'm sloppy and drag past the edge (as I anticipate most users will do), I get the bounce.
Sample console log depicting the bounce:
**** DRAG ****
dragging = true
moveIt : yPos = 712
**** DRAG LEAVE ****
**** DRAG ****
dragging = true
moveIt : yPos = 713
**** DRAG ****
dragging = true
moveIt : yPos = 713
**** DRAG ****
dragging = true
moveIt : yPos = 415
I can switch from HTML5 drag and drop and use a MouseEvent driven style, simply by commenting out the drag and drop listeners and substituting mouse events to avoid this bouncing effect. The fact that things work the way I want with mouse events suggests to me that I'm running into something undocumented with HTML5 DnD. I'm happy to switch to this mouse event driven approach, but then I have to implement scrolling manually and when I try that, the more I drag, the further the draggable moves from the mouse pointer. You can edit the fiddle, commenting out the DnD listeners
I'm happy to have a solution either the DnD or mouse event approaches, but mainly, I want to know what's causing these odd behaviors. Is there something wrong with the way I've structured the page? Is the CSS messed up? Is there some undocumented (but perhaps expected) behavior causing these problems?
I'm making a slide scrolling page, and I'm trying to have it scroll like you're pulling a notecard up and with the next one right behind it.
To do this, I'm making them all fixed, and then moving their "top" position based off of scroll. But then I also need to make the body the size of the panel.
It's hard to describe what I'm doing, so here's the demo: https://codepen.io/NotDan/pen/vzraJE
Here's the particular piece of code that's causing my problem:
//what's going on here?
$(window).scroll(function(){
var panelNum = parseInt($(window).scrollTop()/$(window).height());//detemines panel number
var pixelMovement = ($(window).scrollTop())-(panelNum*$(".panel").height()); determines how many pixels to move the panel by
$('body').find(".panel:eq("+panelNum+")").css("top", -1*pixelMovement);
});
The problem is when the user scrolls quickly, the top position is not set accurately and there's some overhang. Again, hard to explain, but if you jump to the demo and scroll quickly you'll see what I mean.
Is there a more precise way of measuring scroll? Or is there a better way to do what I'm trying to? I've tried scrollmagic, and its "section wipe" feature is really close, but they bring the previous one up rather than move the current one up.
I tried making a condition to determine the panel number and everything started working.
var panelNum = 0;
var pixelMovement = 0;
$(window).scroll(function () {
pixelMovement = $(window).scrollTop() - panelNum * $(".panel").height(); // determines how many pixels to move the panel by
$("body")
.find(".panel:eq(" + panelNum + ")")
.css("top", -1 * pixelMovement);
if (Math.abs(pixelMovement) >= $(window).height()) {
panelNum++;
} else if (pixelMovement <= 0) {
panelNum--;
}
});
Here's the working demo: https://codepen.io/NotDan/pen/RYJeZq
Windows 8 has this neat feature where you scroll through your apps by "pushing" the side of the screen.
I want to know if anyone has any ideas to accomplish this in JavaScript.
Essentially, the screen does should NOT scroll if you hover over the side of the screen, but should rather be able to detect when the user is attempting to go beyond the viewport and cannot.
Is such a thing possible?
Sure, you just need to figure out their algorithm if you want to duplicate it.
You can track the last several known locations of the pointer to determine velocity and direction and stop the scrolling as soon as the direction changes, for example.
I'm using something along the lines of:
$(window).mousemove(function (e) {
if (getIsPageEdge()) {
if (lastX == e.pageX) {
console.debug('pushing the page');
}
var now = new Date().getTime();
if (lastUpdate == null || now - lastUpdate > 500) {
lastUpdate = now;
lastX = e.pageX;
}
}
});
Essentially, onmousemove, if the cursor is at the edge of the viewport, and the X value is not changing (with a time delay added to compensate for the event processing delay), then change the scroll position of the containing div.
I am currently developing a webpage for an iPhone which contains a DIV element that the user can touch and drag around. In addition, when the user drags the element to the top or bottom of the device's screen, I want to automatically scroll the page up or down.
The problem I am having is trying to determine a reliable formula to get the coordinates in the onTouchMove event that coorespond with the user's finger reaching the top or the bottom of the device viewport. My current formula seems tedious and I feel there may be an easier way to do this.
My current formula to determine if the touch event has reached the bottom of the screen:
function onTouchMoveHandler(e)
{
var orientation=parent.window.orientation;
var landscape=(orientation==0 || orientation==180)?true:false;
var touchoffsety=(!landscape?screen.height - (screen.height - screen.availHeight):screen.width - (screen.width - screen.availWidth)) - e.touches[0].screenY + (window.pageYOffset * .8);
if(touchoffsety < 40) alert('finger is heading off the bottom of the screen');
}
I have done a bit of Javascript reflection on objects such as the window, document, body, e.touches to see if I could find a set of numbers that would always add up to equal the top or bottom of the screen, but without reliable success. Help with this would be greatly appreciated.
Assuming the screenY field of a touch holds the y coordinate relative to the screen-top regardless of current scroll position, your current calculation does not make a whole lot of sense to me. I hope I did not misunderstand what your trying to do.
To find out if a touch is close to the top or the bottom of the device, I would first check if screenY is close to top (top being 0), since you can work with that value directly. Then, if it's not close to top, calculate how close it is to the bottom and check that.
var touch = e.touches[0];
if (touch.screenY < 50)
{
alert("Closing in on Top");
}
else //Only do bottom calculations if needed
{
var orientation=parent.window.orientation;
var landscape=(orientation==0 || orientation==180)?true:false;
//height - (height - availHeight) is the same as just using availHeight, so just get the screen height like this
var screenHeight = !landscape ? screen.availHeight : screen.availWidth;
var bottomOffset = screenHeight - touch.screenY; //Get the distance of the touch from the bottom
if (bottomOffset < 50)
alert("Closing in on Bottom");
}
That's actually not bad. You could also use Zepto.js and its built-in touch events and .offset() method to get it a little easier:
http://zeptojs.com/#touch
http://zeptojs.com/#offset
However, I'm interested to know whether or not you actually manage to get it scrolling at the bottom, and if the performance is smooth enough to make the effect worthwhile. (frequently scrolling in iOS interrupts JavaScript really hard)
I am working on a small jQuery plugin that autoscrolls through a set of overflowing elements within a container div based on the mouse position within that container div.
See the Demo Here
The idea is for this plugin to be an improvement of something I wrote a while ago. See the autoscrolling navigation in the lower left here The old problem with this was that it jumps around when you mouseenter from anywhere but the bottom(javascript perspective) of the container div.
Now everything is working fine with my plugin but when you mouseenter from the top it screws up from time to time(move your mouse in and out fast and it will happen for sure), I think this is because I am getting different values from my mouseenter event and my mousemove event which are both used to calculate how to scroll the inner elements. Here is the function, the rest of the source is pretty small and decently commented.
projList.mousemove(function(e){
//keep it from freaking out when we mouseenter from Top of div
if(enterMouseY > divHeight){
enterMouseY = divHeight;
}
mouseY = e.pageY-projList.offset().top;
//ok that didnt work... try to keep it from freaking out when we mouseenter from Top of div
if (mouseY > divHeight){
mouseY = divHeight;
}
//distnace from top of container div to where our mouse Entered
var distToTop = divHeight - enterMouseY;
//here is the calculation, I parameterize the mouseY pos as a value between 0-1
//0 being where we entered and 1 being the top or bottom of the div
//then multiply that by how much we have to scroll to get to the end of the list
//are we moving up or down
if(mouseY>enterMouseY){
//if up calculate based on Top
var dist =totalScrollDistance * ((mouseY-enterMouseY-projList.offset().top)/(distToTop));
}else if(mouseY<enterMouseY){
//if up calculate based on Bottom
var dist =totalScrollDistance * ((mouseY-enterMouseY-projList.offset().top)/(enterMouseY));
}else if(mouseY = enterMouseY){
var dist = 0;
}
//set the position of the list offsetting wherever we left it
pos = dist+lastPos;
//scroll to that position
projList.scrollTop(pos);
//are we trying to scroll past the scrollable amount
if(pos<0){
pos = 0;
}
if(pos>totalScrollDistance){
pos = totalScrollDistance;
}
$('#div1').text("mouseY: "+ mouseY +" enterMouseY: "+ enterMouseY +" distance:"+ dist.toFixed(1) + " pos:"+ pos.toFixed(1));
});
I solved this problem, there was an error in my calculations, but works how I described above.
You can see it in action here
http://web.archive.org/web/20130529212243/http://www.robincwillis.com/AutoScroll/