scroll_events.js (4841B)
1 import { chrome, gecko, ie, mac, presto, safari, webkit } from "../util/browser.js" 2 import { e_preventDefault } from "../util/event.js" 3 4 import { updateDisplaySimple } from "./update_display.js" 5 import { setScrollLeft, updateScrollTop } from "./scrolling.js" 6 7 // Since the delta values reported on mouse wheel events are 8 // unstandardized between browsers and even browser versions, and 9 // generally horribly unpredictable, this code starts by measuring 10 // the scroll effect that the first few mouse wheel events have, 11 // and, from that, detects the way it can convert deltas to pixel 12 // offsets afterwards. 13 // 14 // The reason we want to know the amount a wheel event will scroll 15 // is that it gives us a chance to update the display before the 16 // actual scrolling happens, reducing flickering. 17 18 let wheelSamples = 0, wheelPixelsPerUnit = null 19 // Fill in a browser-detected starting value on browsers where we 20 // know one. These don't have to be accurate -- the result of them 21 // being wrong would just be a slight flicker on the first wheel 22 // scroll (if it is large enough). 23 if (ie) wheelPixelsPerUnit = -.53 24 else if (gecko) wheelPixelsPerUnit = 15 25 else if (chrome) wheelPixelsPerUnit = -.7 26 else if (safari) wheelPixelsPerUnit = -1/3 27 28 function wheelEventDelta(e) { 29 let dx = e.wheelDeltaX, dy = e.wheelDeltaY 30 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail 31 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail 32 else if (dy == null) dy = e.wheelDelta 33 return {x: dx, y: dy} 34 } 35 export function wheelEventPixels(e) { 36 let delta = wheelEventDelta(e) 37 delta.x *= wheelPixelsPerUnit 38 delta.y *= wheelPixelsPerUnit 39 return delta 40 } 41 42 export function onScrollWheel(cm, e) { 43 let delta = wheelEventDelta(e), dx = delta.x, dy = delta.y 44 45 let display = cm.display, scroll = display.scroller 46 // Quit if there's nothing to scroll here 47 let canScrollX = scroll.scrollWidth > scroll.clientWidth 48 let canScrollY = scroll.scrollHeight > scroll.clientHeight 49 if (!(dx && canScrollX || dy && canScrollY)) return 50 51 // Webkit browsers on OS X abort momentum scrolls when the target 52 // of the scroll event is removed from the scrollable element. 53 // This hack (see related code in patchDisplay) makes sure the 54 // element is kept around. 55 if (dy && mac && webkit) { 56 outer: for (let cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { 57 for (let i = 0; i < view.length; i++) { 58 if (view[i].node == cur) { 59 cm.display.currentWheelTarget = cur 60 break outer 61 } 62 } 63 } 64 } 65 66 // On some browsers, horizontal scrolling will cause redraws to 67 // happen before the gutter has been realigned, causing it to 68 // wriggle around in a most unseemly way. When we have an 69 // estimated pixels/delta value, we just handle horizontal 70 // scrolling entirely here. It'll be slightly off from native, but 71 // better than glitching out. 72 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { 73 if (dy && canScrollY) 74 updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)) 75 setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)) 76 // Only prevent default scrolling if vertical scrolling is 77 // actually possible. Otherwise, it causes vertical scroll 78 // jitter on OSX trackpads when deltaX is small and deltaY 79 // is large (issue #3579) 80 if (!dy || (dy && canScrollY)) 81 e_preventDefault(e) 82 display.wheelStartX = null // Abort measurement, if in progress 83 return 84 } 85 86 // 'Project' the visible viewport to cover the area that is being 87 // scrolled into view (if we know enough to estimate it). 88 if (dy && wheelPixelsPerUnit != null) { 89 let pixels = dy * wheelPixelsPerUnit 90 let top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight 91 if (pixels < 0) top = Math.max(0, top + pixels - 50) 92 else bot = Math.min(cm.doc.height, bot + pixels + 50) 93 updateDisplaySimple(cm, {top: top, bottom: bot}) 94 } 95 96 if (wheelSamples < 20) { 97 if (display.wheelStartX == null) { 98 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop 99 display.wheelDX = dx; display.wheelDY = dy 100 setTimeout(() => { 101 if (display.wheelStartX == null) return 102 let movedX = scroll.scrollLeft - display.wheelStartX 103 let movedY = scroll.scrollTop - display.wheelStartY 104 let sample = (movedY && display.wheelDY && movedY / display.wheelDY) || 105 (movedX && display.wheelDX && movedX / display.wheelDX) 106 display.wheelStartX = display.wheelStartY = null 107 if (!sample) return 108 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) 109 ++wheelSamples 110 }, 200) 111 } else { 112 display.wheelDX += dx; display.wheelDY += dy 113 } 114 } 115 }