Maintaining scroll position only works when not near the bottom of messages div

前端 未结 3 1783
故里飘歌
故里飘歌 2021-02-20 16:32

I\'m trying to mimic other mobile chatting apps where when you select the send-message textbox and it opens the virtual keyboard, the bottom-most message is still i

3条回答
  •  花落未央
    2021-02-20 17:28

    My solution is the same as your proposed solution with an addition of conditional check. Here's a description of my solution:

    • Record the last scroll position scrollTop and last clientHeight of .messages to oldScrollTop and oldHeight respectively
    • Update oldScrollTop and oldHeight every time a resize happens on window and update oldScrollTop every time a scroll happens on .messages
    • When window is shrunk (when the virtual keyboard shows), the height of .messages will automatically retract. The intended behaviour is to make the bottommost content of .messages still visible even when .messages' height retracts. This requires us to manually adjust the scroll position scrollTop of .messages.
    • When the virtual keyboard shows, update scrollTop of .messages to make sure that the bottommost part of .messages before its height retraction happens is still visible
    • When the virtual keyboard hides, update scrollTop of .messages to make sure that the bottommost part of .messages remains the bottommost part of .messages after height expansion (unless expansion cannot happen upwards; this happens when you're almost at the top of .messages)

    What caused the problem?

    My (initial possibly flawed) logical thinking is: resize happens, .messages' height changes, update on .messages scrollTop happens inside our resize event handler. However, upon .messages' height expansion, a scroll event curiously happens before a resize! And even more curious, the scroll event only happens when we hide the keyboard when we have scrolled above the maximum scrollTop value of when .messages is not retracted. In my case, this means that when I scroll below 270.334px (the maximum scrollTop before .messages is retracted) and hide the keyboard, that weird scroll before resize event happens and scrolls your .messages to exactly 270.334px. This obviously messes up our solution above.

    Fortunately, we can work around this. My personal deduction of why this scroll before the resize event happens is because .messages cannot maintain its scrollTop position of above 270.334px when it expands in height (this is why I mentioned that my initial logical thinking is flawed; simply because there's no way for .messages to maintain its scrollTop position above its maximum value). Therefore, it immediately sets its scrollTop to the maximum value it can give (which is, unsurprisingly, 270.334px).

    What can we do?

    Because we only update oldHeight on resize, we can check if this forced scroll (or more correctly, resize) happens and if it does, don't update oldScrollTop (because we have already handled that in resize!) We simply need to compare oldHeight and the current height on scroll to see if this forced scrolling happens. This works because the condition of oldHeight not being equal to the current height on scroll will only be true when resize happens (which is coincidentally when that forced scrolling happens).

    Here's the code (in JSFiddle) below:

    window.onload = function(e) {
      let messages = document.querySelector('.messages')
      messages.scrollTop = messages.scrollHeight - messages.clientHeight
      bottomScroller(messages);
    }
    
    
    function bottomScroller(scroller) {
      let oldScrollTop = scroller.scrollTop
      let oldHeight = scroller.clientHeight
    
      scroller.addEventListener('scroll', e => {
        console.log(`Scroll detected:
          old scroll top = ${oldScrollTop},
          old height = ${oldHeight},
          new height = ${scroller.clientHeight},
          new scroll top = ${scroller.scrollTop}`)
        if (oldHeight === scroller.clientHeight)
          oldScrollTop = scroller.scrollTop
      });
    
      window.addEventListener('resize', e => {
        let newScrollTop = oldScrollTop + oldHeight - scroller.clientHeight
    
        console.log(`Resize detected:
          old scroll top = ${oldScrollTop},
          old height = ${oldHeight},
          new height = ${scroller.clientHeight},
          new scroll top = ${newScrollTop}`)
        scroller.scrollTop = newScrollTop
        oldScrollTop = newScrollTop
        oldHeight = scroller.clientHeight
      });
    }
    .container {
      width: 400px;
      height: 87vh;
      border: 1px solid #333;
      display: flex;
      flex-direction: column;
    }
    
    .messages {
      overflow-y: auto;
      height: 100%;
    }
    
    .send-message {
      width: 100%;
      display: flex;
      flex-direction: column;
    }
    hello 1
    hello 2
    hello 3
    hello 4
    hello 5
    hello 6
    hello 7
    hello 8
    hello 9
    hello 10
    hello 11
    hello 12
    hello 13
    hello 14
    hello 15
    hello 16
    hello 17
    hello 18
    hello 19
    hello 20
    hello 21
    hello 22
    hello 23
    hello 24
    hello 25
    hello 26
    hello 27
    hello 28
    hello 29
    hello 30
    hello 31
    hello 32
    hello 33
    hello 34
    hello 35
    hello 36
    hello 37
    hello 38
    hello 39

    Tested on Firefox and Chrome for mobile and it works for both browsers.

提交回复
热议问题