写给自己看的品字形布局main右侧内容和左侧滚动联动配置

由 漆黑菌 于 2023年12月05日 发布

描述

一种2023年很流行的页面布局方式。bilibili的视频播放页。知乎的文章页都是类似设计。类似品字大致可以分为

  1. 最顶部的内容
  2. body主内容,分为左右两侧。

当页面主体部分小于一屏高度时,页面看上去和正常流式布局类似。

当页面主体部分大于一屏高度时。

如果是页面主体左侧大于一屏高度。当页面主体右侧滚动到浏览器顶部时会吸顶,并随之滚动。当主体右侧内容滚动到底时,吸附在浏览器底部

如果是页面主体右侧内容大于一屏高度。页面看上去和正常流式布局类似

核心代码

简化html结构

<div id="home-root"> // 起到类似html的作用占满全屏,100hv+100vw
  <div id="home-page"> // 容器,垂直方向排列的弹性盒子
    <header class="home-header" id="home-header">
        // 见style
      <div class="home-header-container">
      </div>
    </header>
    <main id="home-main">
        // 见style
      <div>
        <header class="main-layout-header">
            // 姑且认为这里随便占位了些内容
        </header>
        <main class="main-layout-main">
          <div class="profile-body">
            <div class="profile-body-left">
                // 一般的弹性盒子
            </div>
            <div class="profile-body-right">
                // 一般的弹性盒子
              <div class="profile-body-right-container" style="isSticky && {
              top: stickyTop + 'px'
            }">
              </div>
            </div>
          </div>
        </main>
      </div>
    </main>
  </div>
</div>

<style>
    #home-header {
        position: sticky;
        top: 0;
    }
    #home-main {
        flex: auto;
        // 默认占满剩下内容的容器
    }
</style>   
    const bodyRightRef = ref<HTMLDivElement>(null);
    const bodyRightContainerRef = ref<HTMLDivElement>(null);
    const homeRootDom = document.getElementById('home-root');
    const pageHeaderDom = document.getElementById('home-header');
    const stickyTop = ref(0);
    const isSticky = ref(false);
    useEventListener(homeRootDom, 'scroll', throttle(() => {
      const el = bodyRightContainerRef.value;
      const bodyEl = bodyRightRef.value;
      if (!el || !bodyEl) {
        return;
      }
      stickyTop.value = window.innerHeight > el.clientHeight
        ? pageHeaderDom.offsetHeight // 这样才能看上去吸在header下面
        : window.innerHeight - el.offsetHeight;// 动态计算右侧下部吸底时的样式
      const {
        height,
      } = el.getBoundingClientRect();
      const { top: bodyTop } = bodyEl.getBoundingClientRect();
      const distance = window.scrollY + window.innerHeight - bodyTop - height;// 计算右侧内容是否吸底
      isSticky.value = distance >= 0 && bodyTop <= 0;// 计算右侧内容吸底||吸顶启动sticky布局
    }, 1000 / 144));