Fossil SCM
chat: improved the 'is previous message currently visible' calculation for the 'should we scroll?' heuristic.
Commit
b3f2eee546f014ead7649c82bc2540388284fe3f4e3299fff6b736480fcdfdca
Parent
dc8f3a3692628ca…
1 file changed
+22
-3
+22
-3
| --- src/chat.js | ||
| +++ src/chat.js | ||
| @@ -7,19 +7,35 @@ | ||
| 7 | 7 | const E1 = function(selector){ |
| 8 | 8 | const e = document.querySelector(selector); |
| 9 | 9 | if(!e) throw new Error("missing required DOM element: "+selector); |
| 10 | 10 | return e; |
| 11 | 11 | }; |
| 12 | - const isInViewport = function(e) { | |
| 12 | + /** | |
| 13 | + Returns true if e is entirely within the bounds of the window's viewport. | |
| 14 | + */ | |
| 15 | + const isEntirelyInViewport = function(e) { | |
| 13 | 16 | const rect = e.getBoundingClientRect(); |
| 14 | 17 | return ( |
| 15 | 18 | rect.top >= 0 && |
| 16 | 19 | rect.left >= 0 && |
| 17 | 20 | rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && |
| 18 | 21 | rect.right <= (window.innerWidth || document.documentElement.clientWidth) |
| 19 | 22 | ); |
| 20 | 23 | }; |
| 24 | + | |
| 25 | + /** | |
| 26 | + Returns true if e's on-screen position vertically overlaps that | |
| 27 | + of element v's. Horizontal overlap is ignored (we don't need it | |
| 28 | + for our case). | |
| 29 | + */ | |
| 30 | + const overlapsElemView = function(e,v) { | |
| 31 | + const r1 = e.getBoundingClientRect(), | |
| 32 | + r2 = v.getBoundingClientRect(); | |
| 33 | + if(r1.top<=r2.bottom && r1.top>=r2.top) return true; | |
| 34 | + else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true; | |
| 35 | + return false; | |
| 36 | + }; | |
| 21 | 37 | |
| 22 | 38 | (function(){ |
| 23 | 39 | let dbg = document.querySelector('#debugMsg'); |
| 24 | 40 | if(dbg){ |
| 25 | 41 | /* This can inadvertently influence our flexbox layouts, so move |
| @@ -212,11 +228,14 @@ | ||
| 212 | 228 | }else{ |
| 213 | 229 | D.append(holder,e); |
| 214 | 230 | this.e.newestMessage = e; |
| 215 | 231 | } |
| 216 | 232 | if(!atEnd && !this.isBatchLoading |
| 217 | - && e.dataset.xfrom!==this.me && !isInViewport(e)){ | |
| 233 | + && e.dataset.xfrom!==this.me | |
| 234 | + && (prevMessage | |
| 235 | + ? !overlapsElemView(prevMessage, this.e.messagesWrapper) | |
| 236 | + : false)){ | |
| 218 | 237 | /* If a new non-history message arrives while the user is |
| 219 | 238 | scrolled elsewhere, do not scroll to the latest |
| 220 | 239 | message, but gently alert the user that a new message |
| 221 | 240 | has arrived. */ |
| 222 | 241 | F.toast.message("New message has arrived."); |
| @@ -247,11 +266,11 @@ | ||
| 247 | 266 | image loads (and we hope it does so before another |
| 248 | 267 | message arrives). |
| 249 | 268 | */ |
| 250 | 269 | if(1===+e.dataset.hasImage){ |
| 251 | 270 | e.querySelector('img').addEventListener('load',()=>e.scrollIntoView()); |
| 252 | - }else if(!prevMessage || (prevMessage && isInViewport(prevMessage))){ | |
| 271 | + }else if(!prevMessage || (prevMessage && isEntirelyInViewport(prevMessage))){ | |
| 253 | 272 | e.scrollIntoView(false); |
| 254 | 273 | } |
| 255 | 274 | } |
| 256 | 275 | }, |
| 257 | 276 | /** Returns true if chat-only mode is enabled. */ |
| 258 | 277 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -7,19 +7,35 @@ | |
| 7 | const E1 = function(selector){ |
| 8 | const e = document.querySelector(selector); |
| 9 | if(!e) throw new Error("missing required DOM element: "+selector); |
| 10 | return e; |
| 11 | }; |
| 12 | const isInViewport = function(e) { |
| 13 | const rect = e.getBoundingClientRect(); |
| 14 | return ( |
| 15 | rect.top >= 0 && |
| 16 | rect.left >= 0 && |
| 17 | rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && |
| 18 | rect.right <= (window.innerWidth || document.documentElement.clientWidth) |
| 19 | ); |
| 20 | }; |
| 21 | |
| 22 | (function(){ |
| 23 | let dbg = document.querySelector('#debugMsg'); |
| 24 | if(dbg){ |
| 25 | /* This can inadvertently influence our flexbox layouts, so move |
| @@ -212,11 +228,14 @@ | |
| 212 | }else{ |
| 213 | D.append(holder,e); |
| 214 | this.e.newestMessage = e; |
| 215 | } |
| 216 | if(!atEnd && !this.isBatchLoading |
| 217 | && e.dataset.xfrom!==this.me && !isInViewport(e)){ |
| 218 | /* If a new non-history message arrives while the user is |
| 219 | scrolled elsewhere, do not scroll to the latest |
| 220 | message, but gently alert the user that a new message |
| 221 | has arrived. */ |
| 222 | F.toast.message("New message has arrived."); |
| @@ -247,11 +266,11 @@ | |
| 247 | image loads (and we hope it does so before another |
| 248 | message arrives). |
| 249 | */ |
| 250 | if(1===+e.dataset.hasImage){ |
| 251 | e.querySelector('img').addEventListener('load',()=>e.scrollIntoView()); |
| 252 | }else if(!prevMessage || (prevMessage && isInViewport(prevMessage))){ |
| 253 | e.scrollIntoView(false); |
| 254 | } |
| 255 | } |
| 256 | }, |
| 257 | /** Returns true if chat-only mode is enabled. */ |
| 258 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -7,19 +7,35 @@ | |
| 7 | const E1 = function(selector){ |
| 8 | const e = document.querySelector(selector); |
| 9 | if(!e) throw new Error("missing required DOM element: "+selector); |
| 10 | return e; |
| 11 | }; |
| 12 | /** |
| 13 | Returns true if e is entirely within the bounds of the window's viewport. |
| 14 | */ |
| 15 | const isEntirelyInViewport = function(e) { |
| 16 | const rect = e.getBoundingClientRect(); |
| 17 | return ( |
| 18 | rect.top >= 0 && |
| 19 | rect.left >= 0 && |
| 20 | rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && |
| 21 | rect.right <= (window.innerWidth || document.documentElement.clientWidth) |
| 22 | ); |
| 23 | }; |
| 24 | |
| 25 | /** |
| 26 | Returns true if e's on-screen position vertically overlaps that |
| 27 | of element v's. Horizontal overlap is ignored (we don't need it |
| 28 | for our case). |
| 29 | */ |
| 30 | const overlapsElemView = function(e,v) { |
| 31 | const r1 = e.getBoundingClientRect(), |
| 32 | r2 = v.getBoundingClientRect(); |
| 33 | if(r1.top<=r2.bottom && r1.top>=r2.top) return true; |
| 34 | else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true; |
| 35 | return false; |
| 36 | }; |
| 37 | |
| 38 | (function(){ |
| 39 | let dbg = document.querySelector('#debugMsg'); |
| 40 | if(dbg){ |
| 41 | /* This can inadvertently influence our flexbox layouts, so move |
| @@ -212,11 +228,14 @@ | |
| 228 | }else{ |
| 229 | D.append(holder,e); |
| 230 | this.e.newestMessage = e; |
| 231 | } |
| 232 | if(!atEnd && !this.isBatchLoading |
| 233 | && e.dataset.xfrom!==this.me |
| 234 | && (prevMessage |
| 235 | ? !overlapsElemView(prevMessage, this.e.messagesWrapper) |
| 236 | : false)){ |
| 237 | /* If a new non-history message arrives while the user is |
| 238 | scrolled elsewhere, do not scroll to the latest |
| 239 | message, but gently alert the user that a new message |
| 240 | has arrived. */ |
| 241 | F.toast.message("New message has arrived."); |
| @@ -247,11 +266,11 @@ | |
| 266 | image loads (and we hope it does so before another |
| 267 | message arrives). |
| 268 | */ |
| 269 | if(1===+e.dataset.hasImage){ |
| 270 | e.querySelector('img').addEventListener('load',()=>e.scrollIntoView()); |
| 271 | }else if(!prevMessage || (prevMessage && isEntirelyInViewport(prevMessage))){ |
| 272 | e.scrollIntoView(false); |
| 273 | } |
| 274 | } |
| 275 | }, |
| 276 | /** Returns true if chat-only mode is enabled. */ |
| 277 |