skip to Main Content

I have a DIV container that would act as a log container in practice. This has overflow: auto and has the flex-direction: column-reverse to display the log entries from bottom to top in chronological order.

I have these log entries as a JSON structure and extract the text line.text of the message itself and if available another JSON structure with line.JSON. If a JSON structure is present, a button is displayed, with which one can show or hide a corresponding <pre> element in which one sees the formatted JSON code.

Now I have the problem that when I use the button to expand a log entry, the text moves upwards so that you end up at the bottom of the <pre> element. For useablilty reasons this is bad. This is probably due to the CSS property column-reverse.

Gif that shows behavior

The intended behavior would be that the scrollbar expands downwards and you don’t lose the view on the button. So that visually nothing changes for the user, except that this very element unfolds towards the bottom.

I have already tried that I set the ‘scrollTop’ position to the height of the button but this is also a rather annoying behavior.

if(logContainer && button) {
    logContainer.scrollTop = button.offsetTop - logContainer.offsetTop; 
}

So my question is: How can i manipulate the scrollbar behavior to fullfill these requirements?

I have created a minimal reproduced example on Stackblitz: here

2

Answers


  1. To ensure the button will remain at the same point relative to the container even if the content grows/shrinks, you need to grab its offsetTop before the change gets rendered and later find the difference with its offsetTop after the content changed.

    setTimeout() will make sure the UI will be redrawn before running the passed callback.

    That way you will have a correct difference to apply to the container scrollTop.

    Just change this function in your code and you’ll see the new behaviour with the mouse still hovering the button in both cases when you expand/collapse a node:

    toggleJsonDisplay(index: number) {
        const logContainer = this.logContainer.nativeElement;
        const button = document.getElementById(`jsonDisplayButton${index}`);
    
        const originalOffsetTop = button.offsetTop;
        const originalDistanceFromViewport =
            originalOffsetTop - logContainer.scrollTop;
    
        this.showJsonMap[index] = !this.showJsonMap[index];
    
        setTimeout(() => {
            logContainer.scrollTop = button.offsetTop - originalDistanceFromViewport;
        });
    }
    

    Plus if you add the following styling it will be easier to understand what’s going on because each button will have an index on top that you can see to understand the scrolling is working correctly:

    .log-container {
      counter-reset: buttonCounter;
    }
    
    .log-container button:before {
      counter-increment: buttonCounter;
      content: counter(buttonCounter);
      position: absolute;
      top: -2em;
      left: 50%;
      transform: translateX(-50%);
      background-color: black;
      border-radius: 50%;
      padding: 2px 5px;
      font-size: 12px;
      border: solid 2px red;
      color: white;
      font-weight: 600;
    }
    
    .log-container button {
      position: relative;
    }
    
    Login or Signup to reply.
  2. As per my comment, you can achieve exactly the same behavior by reversing the order of the items in the array and using flex-direction: column.

    You can achieve this by reversing the order of the lines after you initialize them:

    this.lines.reverse();
    

    And then simply changing your styling in .log-container to:

    flex-direction: column;
    

    I’ve forked your stackblitz here and added both solutions, you can switch them clicking the button at the top. I’ve added index to each line for easier following of what happens.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search