Scrolling Container Following Down the Page, Halts at Its Own Bottom (Similar to Twitter)

I am facing an issue and need some help. I am working on creating a three-column page layout where the middle section is for posts, the right section is for links and references (a bit long), and the left section is fixed.

My question is: How can I prevent the right div from moving when it reaches its bottom? Also, if the content in the middle div is shorter than the right div, how do I add a scrollbar to the page just for the right div, similar to what Twitter does?

I have been brainstorming and thought that maybe Twitter uses two separate divs for those sections. One acts normally, while the other remains fixed at the bottom. The normal one stretches the page for scrolling, while the fixed one sticks on top of it. However, I am unsure if this is the correct approach.

Is it possible to achieve this with pure CSS? (I am using TailwindCSS)

Here is my idea presented visually. (Alternatively, you can look at the Twitter homepage feed)

https://i.stack.imgur.com/zHMmO.jpg

https://i.stack.imgur.com/Sgleo.jpg

Additionally, here is a gif demonstration:

click

Answer №1

Some of the solutions offered here may not be ideal. To achieve this, you need to add an event listener for window scroll.

The solution you are seeking is a "scrolling sticky sidebar". You can find a neat implementation with minimal code here.

Here are the key steps:

Step One:

Create a sidebar container and a subcontainer wrapper

<div class="sidebar">
  <div class="content-wrapper">
    <!-- content -->
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</div>

Step Two:

Select the elements and monitor scroll positions before adjusting values as needed

let sidebar = document.getElementsByClassName("sidebar")[0];
let sidebar_content = document.getElementsByClassName("content-wrapper")[0];

window.onscroll = () => {
        let scrollTop = window.scrollY; 
        let viewportHeight = window.innerHeight; 
        let contentHeight = sidebar_content.getBoundingClientRect().height; 
        let sidebarTop = sidebar.getBoundingClientRect().top + window.pageYOffset; 
}

if(scrollTop >= contentHeight - viewportHeight + sidebarTop) {
  sidebar_content.style.transform = `translateY(-${contentHeight - viewportHeight + sidebarTop}px)`;
  sidebar_content.style.position = "fixed";
}
else {
  sidebar_content.style.transform = "";
  sidebar_content.style.position = "";
}

Outcome:

https://i.stack.imgur.com/hbZGv.gif

For further details, refer to the link provided above.

Answer №2

If you're searching for a solution, look no further. Here is what you need to do:

1. Utilize Flexbox and the position:sticky

To achieve the desired effect, make use of the flexibility that comes with a flexbox layout. Set both the position:sticky property and specify an align-self value based on the user's scrolling behavior. For the next step involving JavaScript to work effectively, this initial setup is crucial.

2. Employ JavaScript during scrolling to adjust margin, position, and align-self

The custom JavaScript code monitors changes in the scrolling direction. When such changes occur, it does the following:

  1. Changes the align-self property to either flex-end (while scrolling up) or flex-start (while scrolling down).
  2. Sets the bottom value (while scrolling up) or the top value (while scrolling down) to the calculated result of window height - sidebar height. This accounts for the portion of the sidebar not visible on-screen when scrolling.
  3. Determines the remaining space available at the top (during downward scrolls) or at the bottom (during upward scrolls). It then assigns this as a top or bottom margin to maintain the sidebar's visual position without disruption.

By setting these margins, the sidebar can smoothly scroll back into view immediately once the user starts moving upwards again, eliminating the need for them to manually reach the top to release the sticky position.

Live Demonstration

Check out the live demo below, accessible via Codepen, or visit my personal website for more examples at this link.

UPDATE: Following feedback from damzaky, I have incorporated additional JavaScript logic in order to ensure the sidebar resumes scrolling upwards automatically whenever necessary.

UPDATE 2: Substantial modifications have been made to the JavaScript code resulting in improved functionality (as per current observations).

Answer №3

If you're looking to create a sticky sidebar, consider using the JavaScript library called sticky-sidebar.

var sidebar = new StickySidebar('#sidebar', {
  containerSelector: '#main',
  innerWrapperSelector: '.sidebar__inner',
});
body {
  background: #3f87c2;
  margin: 0;
  padding: 0;
  text-align: center;
}

#main {
  display: flex;
}

#middle {
  flex-grow: 1;
  background: #4dd2ff;
}

#sidebar {
  min-height: 1000px;
  width: 200px;
  background: #4dd2ff;
  margin-left: 15px;
}

.sidebar {
  will-change: min-height;
}

.sidebar__inner {
  transform: translate(0, 0);
  /* For browsers don't support translate3d. */
  transform: translate3d(0, 0, 0);
  will-change: position, transform;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/sticky-sidebar/3.3.1/sticky-sidebar.min.js" integrity="sha512-iVhJqV0j477IrAkkzsn/tVJWXYsEqAj4PSS7AG+z1F7eD6uLKQxYBg09x13viaJ1Z5yYhlpyx0zLAUUErdHM6A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="main">
  <div id="middle">
    <div>Middle Div Content (Sooo long)</div>
    <div style="margin-top: 2400px">End of Middle Content</div>
  </div>
  <div id="sidebar" class="sidebar">
    <div class="sidebar__inner">
      <div>Right Div Content</div>
      <div style="margin-top: 900px">Right Div Content Bottom</div>
    </div>
  </div>
</div>

You could attempt to create this from scratch, but using this library ensures compatibility and performance optimizations. Check out the full documentation here.

Answer №4

To prevent an element from scrolling, you can apply the following CSS code: position: sticky; bottom: 0

For more information on how the "position: sticky;" property works, check out this post on Stackoverflow How does the "position: sticky;" property work?

I hope this helps answer your question!

Edit: [Give it a try]

.main {
  width: 100%;
  height: 1000px;
  display: flex;
}

.first {
  width: 30%;
  background-color: red;
}

.second {
  width: 40%;
  background-color: green;
}

.third {
  width: 30%;
  background-color: blue;
  height: 500px;
  position: sticky; 
  top: 0px;
}

p {
  margin-left: 20px;
}
<div class="main">
  <div class="first">
    <p>
      Left content.
    </p>
  </div>

  <div class="second">
    <p>
      Main content.
    </p>
  </div>
  <div class="third">
    <p>
      Right content.
    </p>
  </div>
</div>

Answer №5

My investigation into the HTML and CSS structure of Twitter using developer tools revealed an interesting implementation. I attempted to replicate this implementation by creating three vertical sections, with the middle and right sections enclosed within a parent div similar to how it is done on Twitter. This setup results in the scrollbar appearing on the parent div instead of the individual sections, providing a seamless scrolling experience. Feel free to test the functionality by scrolling through the middle section (scrollbars are hidden).

Additional Information:

  • The passive: true option in the scrollbar event is utilized to enhance browser performance for the scroll event, as scroll events can be resource-intensive.

  • If the content in your right section is limited, like on Twitter, and there is minimal scrolling involved, consider implementing the IntersectionObserver method highlighted in the referenced CSS Tricks article for improved performance. This approach allows for smoother scrolling when reaching the bottom or top of the right section, enhancing the overall user experience.

    right.scrollTo({
        top: right.scrollHeight,
        behavior: "smooth",
    });
    

For more insights, refer to this answer where I employ the IntersectionObserver technique to display a button based on specific criteria.

Should you have any queries or require further clarification, feel free to inquire in the comments section below.

const content = document.querySelector('.content-container');
const right = document.querySelector('.right');

const scrollSpeed = 1;

console.log(right);

content.addEventListener("scroll", (e) => {
  console.log(content.scrollTop);
  right.scrollTop = content.scrollTop * scrollSpeed;
}, {
  passive: true
})
body {
  margin: 0;
  padding: 0;
}

.main {
  display: flex;
  height: 100vh;
  overflow-y: hidden;
}

.middle {
  flex: 1;
  height: auto;
}

.left,
.right {
  width: 150px;
  max-height: 100vh;
  overflow-y: auto;
}

.left {
  background: gray;
}

.left,
.middle,
.right {
  padding: 1rem;
}

.right {
  background: lightgray;
  position: sticky;
  top: 0;
  overflow-y: auto;
  padding: 0;
  padding: 1rem;
}

.content-container {
  flex: 1;
  display: flex;
  position: relative;
  overflow-y: auto;
  z-index: 0;
  background: whitesmoke;
}

.right::-webkit-scrollbar,
.left::-webkit-scrollbar,
.content-container::-webkit-scrollbar {
  display: none;
}

.right,
.left,
.content-container {
  -ms-overflow-style: none;
  /* Internet Explorer 10+ */
  scrollbar-width: none;
  /* Firefox */
}


/* helper classes to overflow containers */

.menu {
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  list-style: none;
  font-size: 1.1rem;
}

.middle-section-content {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}

.middle-section-content div {
  min-height: 150px;
}
<div class="main">
  <div class="left">
    <h2>
      First
    </h2>
    <ul class="menu">
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
      <li>
        Menu Item
      </li>
    </ul>
    <div>
      End of Left Section
    </div>
  </div>
  <div class="content-container">
    <div></div>
    <div class="middle">
      <h2>
        Middle Section
      </h2>

      <div class="middle-section-content">

        <div>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</div>
        <div>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</div>
        <div>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</div>
        <div>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</div>
        <div>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</div>
        <div>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</div>
        <div>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</div>

      </div>
      <div>
        End of Middle Section
      </div>

    </div>
    <div class="right">
      <h2>
        Right
      </h2>
      <ul class="menu">
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <li>
          Side Bar Content Item
        </li>
        <div>
          End of Right Section
        </div>
      </ul>
    </div>
  </div>

</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to attach multiple files in PHP using mail.php and mime.php from Pear library?

I am having trouble attaching multiple files. When I try to attach more than one file, only one file gets sent in the email even though I attached multiple files. if(isset($_POST['upload'])){ include('config.php'); include_once ...

"Learn how to compile a single jade file using grunt-jade instead of compiling all files at once

In my jade.js file, the code looks like this: 'use strict'; var config = require('../config'); module.exports = { dist: { options: { pretty: true, debug: false, timestamp: '<%= new Date().getTime() %>&apo ...

Customizing Passport JS Bearer strategy for various endpoints and HTTP REST operations

I'm currently using the BearerStrategy and I am attempting to configure different strategies for each endpoint or method within the same router. After reviewing the documentation, I have not come across any references to this particular scenario othe ...

The href attribute is not functioning correctly on Internet Explorer version 8

When dynamically generating HTML and appending the response to a DIV, here is the jQuery code: {% elif topic|length < 1 or topic.user_exhausted_attempts %} $('.questionHolder').hide(); var html = '<section class="span9">&a ...

Troubleshooting Bootstrap Social Buttons: Background Color and Hovering Challenges

I've been working with Bootstrap to design social buttons for Facebook and LinkedIn. However, I'm facing an issue where setting the background color to white for the buttons doesn't cover them properly. The white background extends beyond th ...

What is the best way to filter an object in AngularJS based on its properties?

I am faced with the issue of filtering two arrays of objects. Below is an example: $scope.countries = [ {'id' : 1, 'country_name':'India'}, {'id' : 10, 'country_name':'Australia'} ...

Steer clear of utilizing CSS pseudo-elements like :before and :after

I am seeking a solution to hide a <label> when viewing on a small mobile device. Below is the HTML code: <label class="page_createpassword_label" for="page_createpassword"> <span class="page_label_main">Span Text</span> <span c ...

Limiting the style of an input element

How can I mask the input field within an <input type="text" /> tag to restrict the user to a specific format of [].[], with any number of characters allowed between the brackets? For example: "[Analysis].[Analysis]" or another instance: "[Analysi ...

Saving Arrays through an Input Form in JavaScript

I am working with an HTML form that looks like the following: <div class="form-group"> <label for="first_name">1st Name</label> <input type="text" id="first_name" placeholder="First Name" class="form-control"/> </div> ...

Can basic logic be applied to CSS?

Recently, I have been experimenting with CSS to blur NSFW images in a chatroom until they are hovered over. I have successfully accomplished this task and now my next goal is to implement checkboxes that can toggle the display of these images. I am aware o ...

Basic selection menu layout

I have put together a basic menu for my WordPress site without relying on classes or IDs: <div class="main-menu"> <nav> <ul> <li> <a href="#" class="menu-link">home</a> ...

What is the significance of $($(this)) in coding jargon

While browsing the internet, I came across a code snippet that includes the following line: if ($($(this)).hasClass("footer_default")) { $('#abc') .appendTo($(this)) .toolbar({position: "fixed"}); } I'm curious ab ...

Can a placeholder dropzone be created at the bottom of Vue Draggable lists?

Currently, I am utilizing Vue.Draggable (accessible at https://github.com/SortableJS/Vue.Draggable) to develop a kanban board that enables me to drag items from one list to another. A challenge I face is that when dragging a list item to the bottom of anot ...

PHP POST data not displaying in output

Having an issue with displaying the chosen value from a database enum in a text field on my website. The enum table has options like Bachelor of Science, Master of Science, Bachelor of Education, and Master of Education. I want to show the selected option ...

Utilize jQuery to retrieve data attributes and set them as CSS properties in a key-value format

Looking at the HTML below: <h4 data-padding-top="100" data-padding-left="0" data-text-align="left" data-color="#ffffff">Promotion Two</h4> I aim to use the data attributes as CSS styles for the H4 element. I have attempted to achieve this ...

Search the table for checked boxes and textboxes that are not empty

Could you suggest alternative ways to express the following scenario? I have a table with 3 rows. Each row contains a column with 3 checkboxes and another column with just a text box. I would like it so that when the values are retrieved from the database ...

The shared module for next/router is not found in the shared scope default within Next.js and @module-federation/nextjs-mf

I am working on a JavaScript Turbo repo with a host app that has the following configuration in its next.config.js: const { NextFederationPlugin } = require("@module-federation/nextjs-mf"); const nextConfig = { reactStrictMode: true, ...

Open a Link in the Default Browser with a Right Click in node-webkit

I'm using an iframe in my node-webkit app. I want users to have the ability to right-click on links within the iframe (a tags) and choose an option like "Open in Browser" to open the link in their default browser. Can this be done? ...

Modifying content on the fly with a dropdownlist in Knockout framework

Currently experimenting with implementing inline editing through knockout. I stumbled upon this informative thread about Knockout Inline Edit Binding After some tweaks to introduce a "Select" for edit mode, the functionality seems to be working fine. Howe ...

Dynamic Component Interactions in VueJS using Mouse Events

Just starting out with Vue and other frameworks, so my approach may not be very "Vue-like". I am attempting to create a versatile button component that can have different behaviors based on a prop, in order to maintain just one button component. The desir ...