Skip to content

Commit

Permalink
Reducing wheel-scroll flickering, take eleven
Browse files Browse the repository at this point in the history
Issue #881

Seeds the delta-to-pixel conversion with a value on browsers where
we (possibly) know it. Don't stop the actual wheel event, but let
it scroll normally, but use the estimated scroll delta to proactively
draw the content that will be scrolled into view.

Closes #913
  • Loading branch information
marijnh committed Oct 30, 2012
1 parent ff99211 commit 5d3a42e
Showing 1 changed file with 34 additions and 24 deletions.
58 changes: 34 additions & 24 deletions lib/codemirror.js
Expand Up @@ -324,12 +324,13 @@ window.CodeMirror = (function() {
d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
}

function visibleLines(display, doc, scrollTop) {
var top = (scrollTop != null ? scrollTop : display.scroller.scrollTop) - paddingTop(display);
var fromHeight = Math.max(0, Math.floor(top));
var toHeight = Math.ceil(top + display.wrapper.clientHeight);
return {from: lineAtHeight(doc, fromHeight),
to: lineAtHeight(doc, toHeight)};
function visibleLines(display, doc, viewPort) {
var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
if (typeof viewPort == "number") top = viewPort;
else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
top = Math.floor(top - paddingTop(display));
var bottom = Math.ceil(top + height);
return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
}

// LINE NUMBERS
Expand Down Expand Up @@ -368,23 +369,23 @@ window.CodeMirror = (function() {

// DISPLAY DRAWING

function updateDisplay(cm, changes, scrollTop) {
function updateDisplay(cm, changes, viewPort) {
var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo;
var updated = updateDisplayInner(cm, changes, scrollTop);
var updated = updateDisplayInner(cm, changes, viewPort);
if (updated) {
signalLater(cm, cm, "update", cm);
if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
signalLater(cm, cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
}
updateSelection(cm);
updateScrollbars(cm.display, cm.view.doc.height, scrollTop);
updateScrollbars(cm.display, cm.view.doc.height, typeof viewPort == "number" ? viewPort : null);
return updated;
}

// Uses a set of changes plus the current scroll position to
// determine which DOM updates have to be made, and makes the
// updates.
function updateDisplayInner(cm, changes, scrollTop) {
function updateDisplayInner(cm, changes, viewPort) {
var display = cm.display, doc = cm.view.doc;
if (!display.wrapper.clientWidth) {
display.showingFrom = display.showingTo = display.viewOffset = 0;
Expand All @@ -394,7 +395,7 @@ window.CodeMirror = (function() {
// Compute the new visible window
// If scrollTop is specified, use that to determine which lines
// to render instead of the current scrollbar position.
var visible = visibleLines(display, doc, scrollTop);
var visible = visibleLines(display, doc, viewPort);
// Bail out if the visible area is already rendered and nothing changed.
if (changes !== true && changes.length == 0 &&
visible.from > display.showingFrom && visible.to < display.showingTo)
Expand Down Expand Up @@ -1552,11 +1553,20 @@ window.CodeMirror = (function() {
// and, from that, detects the way it can convert deltas to pixel
// offsets afterwards.
//
// The reason we want to directly handle the wheel event is that it
// gives us a chance to update the display before the actual
// scrolling happens, reducing flickering.
// The reason we want to know the amount a wheel event will scroll
// is that it gives us a chance to update the display before the
// actual scrolling happens, reducing flickering.

var wheelSamples = 0, wheelDX, wheelDY, wheelStartX, wheelStartY, wheelPixelsPerUnit = null;
// Fill in a browser-detected starting value on browsers where we
// know one. These don't have to be accurate -- the result of them
// being wrong would just be a slight flicker on the first wheel
// scroll (if it is large enough).
if (ie) wheelPixelsPerUnit = -.53;
else if (gecko) wheelPixelsPerUnit = 15;
else if (chrome) wheelPixelsPerUnit = -.7;
else if (safari) wheelPixelsPerUnit = -1/3;

var wheelSamples = [], wheelDX, wheelDY, wheelStartX, wheelStartY, wheelPixelsPerUnit;
function onScrollWheel(cm, e) {
var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
Expand All @@ -1565,10 +1575,13 @@ window.CodeMirror = (function() {

var scroll = cm.display.scroller;
if (wheelPixelsPerUnit != null) {
if (dx) setScrollLeft(cm, Math.max(0, Math.round(scroll.scrollLeft += dx * wheelPixelsPerUnit)));
if (dy) setScrollTop(cm, Math.max(0, Math.round(scroll.scrollTop + dy * wheelPixelsPerUnit)));
e_stop(e);
} else {
var pixels = dy * wheelPixelsPerUnit;
var top = cm.view.scrollTop, bot = top + cm.display.wrapper.clientHeight;
if (pixels < 0) top = Math.max(0, top + pixels);
else bot = Math.min(cm.view.doc.height, bot + pixels);
updateDisplay(cm, [], {top: top, bottom: bot});
}
if (wheelSamples < 20) {
if (wheelStartX == null) {
wheelStartX = scroll.scrollLeft; wheelStartY = scroll.scrollTop;
wheelDX = dx; wheelDY = dy;
Expand All @@ -1579,11 +1592,8 @@ window.CodeMirror = (function() {
(movedX && wheelDX && movedX / wheelDX);
wheelStartX = wheelStartY = null;
if (!sample) return;
wheelSamples.push(sample);
if (wheelSamples.length >= 15) {
for (var total = 0, i = 0; i < wheelSamples.length; ++i) total += wheelSamples[i];
wheelPixelsPerUnit = total / wheelSamples.length;
}
wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
++wheelSamples;
}, 200);
} else {
wheelDX += dx; wheelDY += dy;
Expand Down

0 comments on commit 5d3a42e

Please sign in to comment.