Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scrolling is unfriendly #82

Open
Snoturky opened this issue Nov 7, 2016 · 7 comments
Open

Scrolling is unfriendly #82

Snoturky opened this issue Nov 7, 2016 · 7 comments

Comments

@Snoturky
Copy link

Snoturky commented Nov 7, 2016

Scrolling seems to work by looking at how close the cursor is to the viewable part of a scrollable parent on each mousemove event. The closer it is to the edge, the more it scrolls for that single fire of the event. It is intuitive that the scroll speed is based upon proximity to the edge, but it is not intuitive that scrolling happens only after each pixel-by-pixel move of the cursor.

That means that holding an element steady at the edge of a scrollable parent does not scroll at all, but dragging the element back into the center of the scrollable parent (which usually indicates that you have found what you were looking for and want to stop scrolling) will cause you to scroll away.

Is there a way to make the scrolling smooth, so it will continuously scroll at some constant speed depending upon where the element is held, instead of only scrolling as the mouse continues to move?

This is especially troublesome when you pick up an element near the edge, because just the act of picking it up and moving it toward the middle of the scrollable area will cause it to scroll.

@kornelski
Copy link
Owner

I agree, it is problematic. It probably should keep updating continuously. Updating on mousemove makes scrolling dependent also on the rate browser sends updates, so it can't be relied upon.

Currently there's no solution for this proposed.

@MarkHill89
Copy link

Not sure if this is anything related or not, but when using Slip for a Cordova application that I am putting together, the android build has had some terrible interactions. In can only move down the screen and not up, when recording list items. The selecting of the list item is very picky. If you scroll down have more list items than what is on the screen, it goes straight to the bottom of the list, and not smoothly in any fashion. These are just a few of the problems.

@Sauvage9
Copy link

Sauvage9 commented Apr 12, 2017

Here's a possible solution.
It works by dividing the triggerzone into 10 parts and use the offset within the zone as a multiplier for scroll animation duration. Currently animation is done using requestAnimationFrame but can be polyfilled using setTimeout. It still needs some fine-tuning but it will perform as it should.

Add following to prototype:
scrollEvt:{}, // object holding all required scroll params and scroll state

Replace following in prototype:

updateScrolling: function() {
	if(!this.target || this.scrollEvt.scrolling){return};

	var triggerOffset = 100,
		duration=100,
		offset = 0,
		that=this;

	var scrollable = this.target.scrollContainer,
		containerRect = scrollable.getBoundingClientRect(),
		targetRect = this.target.node.getBoundingClientRect(),
		bottomOffset = Math.min(containerRect.bottom, window.innerHeight) - targetRect.bottom,
		topOffset = targetRect.top - Math.max(containerRect.top, 0),
		maxScrollTop = this.target.origScrollHeight - Math.min(scrollable.clientHeight, window.innerHeight);

	if (bottomOffset < triggerOffset) {
	  offset = Math.min(triggerOffset, triggerOffset - bottomOffset);
	}
	else if (topOffset < triggerOffset) {
	  offset = Math.max(-triggerOffset, topOffset - triggerOffset);
	}

	if(offset){
		this.scrollEvt.scrolling=true;
		var speed=Math.abs(Math.ceil((offset*10)/triggerOffset)),speedarray=[5,5,5,4,4,3,3,2,2,1,1];
		this.animateScrolling(scrollable,this.target.node.offsetHeight,scrollable.scrollTop,duration*speedarray[speed],offset<0?-1:1);
	}
},

animateScrolling:function(scrollable,height,ypos,duration,direction,starttime,timestamp){
	var that=this,progress=0;
	if(timestamp){
		var starttime=starttime||timestamp;progress=(timestamp-starttime)/duration;if(progress>1){progress=1};scrollable.scrollTop=(ypos+height*progress*direction);
		this.state.onMove.call(this);
	}
	if(progress==1){this.cancelAnimateScrolling();this.updateScrolling();return};
	this.scrollEvt.raf=requestAnimationFrame(function(timestamp){that.animateScrolling(scrollable,height,ypos,duration,direction,starttime,timestamp)});

},

cancelAnimateScrolling:function(){
	this.scrollEvt.scrolling=false;
	cancelAnimationFrame(this.scrollEvt.raf);
},

updatePosition: function(e, pos) {
	if (this.target == null) {
		return;
	}
	this.latestPosition = pos;

	if (this.state.onMove && !this.scrollEvt.scrolling) {
		if (this.state.onMove.call(this) === false) {
			e&&e.preventDefault();
		}
	}

	//sample latestPosition 100ms for velocity
	if (this.latestPosition.time - this.previousPosition.time > 100) {
		this.previousPosition = this.latestPosition;
	}
},

onMouseUp: function(e) {
	if (this.usingTouch || e.button !== 0) return;
	if (this.state.onEnd && false === this.state.onEnd.call(this)) {
		e.preventDefault();
	}
	this.cancelAnimateScrolling();
},

onTouchEnd: function(e) {
	if (e.touches.length > 1) {
		this.cancel();
	} else if (this.state.onEnd && false === this.state.onEnd.call(this)) {
		e.preventDefault();
	}
	this.cancelAnimateScrolling();
},

When scrolling, scrollEvt.scrolling=true. This is useful to block other events and avoid duplicate calls. animateScrolling will call onmove since scrolling with a steady mouse will not, resulting in jagged movement.

TODO:

  • fix needed for FireFox
  • scroll delay needed for startMove instant items in the trigger zone

@Sauvage9
Copy link

Sauvage9 commented Apr 13, 2017

Update:

  • Changed triggerzone into 4 parts instead of 10 for smoother scrolling.
  • Added movement check on instant items in the triggerzone so scrolling won't begin until up/down movement was detected.
updateScrolling: function() {
	if(!this.target || this.scrollEvt.scrolling){return};
	
	var triggerOffset = 100,
		duration=100,
		offset = 0,
		that=this;

	var scrollable = this.target.scrollContainer,
		containerRect = scrollable.getBoundingClientRect(),
		targetRect = this.target.node.getBoundingClientRect(),
		bottomOffset = Math.min(containerRect.bottom, window.innerHeight) - targetRect.bottom,
		topOffset = targetRect.top - Math.max(containerRect.top, 0),
		maxScrollTop = this.target.origScrollHeight - Math.min(scrollable.clientHeight, window.innerHeight);
		
	if (bottomOffset < triggerOffset) {
	  offset = Math.min(triggerOffset, triggerOffset - bottomOffset);
	}
	else if (topOffset < triggerOffset) {
	  offset = Math.max(-triggerOffset, topOffset - triggerOffset);
	}
	
	if(!offset){return}
	if(offset<0 && this.getTotalMovement().y>=-(triggerOffset/10)){return}
	if(offset>0 && this.getTotalMovement().y<=(triggerOffset/10)){return}
	
	this.scrollEvt.scrolling=true;
	var speed=Math.abs(Math.ceil((offset*4)/triggerOffset));
	this.animateScrolling(scrollable,this.target.node.offsetHeight,scrollable.scrollTop,duration*[4,4,3,2,1][speed],offset<0?-1:1)
},

TODO:

  • Firefox doesn't like scrollTo on document.body

@Sauvage9
Copy link

Firefox autoscroll fix:

Add following after damnYouChrome declaration:
var damnYouFirefox = /Firefox/.test(navigator.userAgent);

Add following after scrollContainer = scrollContainer || document.body;:
if(damnYouFirefox && scrollContainer==document.body) {scrollContainer=document.documentElement};

@Webifi
Copy link

Webifi commented Sep 19, 2017

@Snoturky

Could you do a proper pull request for your changes?

@carter-thaxton
Copy link
Collaborator

Is it even necessary to check for Firefox, or can we just always use documentElement?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants