Note-45089-22

Token ID: 1

ERC-721 1 Transfers

Metadata

{
  "title": "实现滚动时Header自动隐藏",
  "tags": [
    "post",
    "技术"
  ],
  "sources": [
    "xlog"
  ],
  "external_urls": [
    "https://daidr.xlog.app/hidable-header"
  ],
  "date_published": "2023-02-02T02:50:00.000Z",
  "content": "![image](ipfs://bafkreid3oszgfsqt2bz4rq36oi7ttlsqkumfovnkzeaztupn5t3wvbfhy4)\n\n这是掘金网页版的头部,当滚动条向下滑动时,主header会隐藏,次级header会吸在页面顶部。\n\n![image](ipfs://bafkreifido5w72i4de2rsbk2tvq6ti2b6dhtxrw6gdf2sst63vvy2ixqou)\n\n## 样式\n\n首先,我们先通过两个 div 来模拟这两个header\n\n    <template>\n        <div class=\"main-header-wrapper\">\n          <div class=\"main-header\"></div>\n        </div>\n      <div class=\"sub-header-wrapper\">\n        <div class=\"sub-header\"></div>\n      </div>\n      <div v-for=\"i of 100\" :key=\"i\">测试滚动</div>\n    </template>\n    \n    <style scoped>\n      .main-header-wrapper {\n        height: 50px;\n      }\n      \n      .main-header {\n        height: 50px;\n        width: 100%;\n        background: #ff0000;\n        position: fixed;\n        top: 0;\n      }\n      \n      .sub-header-wrapper {\n        height: 40px;\n      }\n      \n      .sub-header {\n        height: 40px;\n        width: 100%;\n        background: #00ff00;\n        position: fixed;\n        top: 50px;\n      }\n    </style>\n\n\n![image](ipfs://bafkreibwpq72cqwia4js7s6edpsgtblg7efblifjt4sr7aiydw4nqaolyi)\n\n这里使用绝对定位来将两个header固定在页面顶部,这两个div分别是`main-header`和`sub-header`。他们两个分别用容器`main-header-wrapper`和`sub-header-wrapper`包裹,是为了防止由于header脱离文档流导致遮住正文元素。\n\n## 简化\n\n不妨把滚动方向的检测放到一边,先实现这样的效果:为两个header加上类名`hidden`的时候,主header隐藏,次级header吸顶。这里可以直接用transform来实现。\n\n为了方便演示效果,增加了一个按钮,用来添加和删除`hidden`类名。\n\n```html\n    <script setup>\n      import { ref } from 'vue';\n      \n      const isHidden = ref(false);\n    </script>\n    \n    <template>\n        <div class=\"main-header-wrapper\">\n          <div class=\"main-header\" :class=\"{'hidden':isHidden}\"></div>\n        </div>\n      <div class=\"sub-header-wrapper\">\n        <div class=\"sub-header\" :class=\"{'hidden':isHidden}\"></div>\n      </div>\n      <div v-for=\"i of 100\" :key=\"i\">测试滚动</div>\n      <button style=\"position:fixed;bottom: 10px; right: 10px;\" @click=\"isHidden = !isHidden\">\n        切换\n      </button>\n    </template>\n    \n    <style>\n      html, body {\n        margin: 0;\n        padding: 0;\n      }\n    </style>\n    \n    <style scoped>\n      .main-header-wrapper {\n        height: 50px;\n      }\n      \n      .main-header {\n        height: 50px;\n        width: 100%;\n        background: #ff0000;\n        position: fixed;\n        top: 0;\n      }\n      \n      .sub-header-wrapper {\n        height: 40px;\n      }\n      \n      .sub-header {\n        height: 40px;\n        width: 100%;\n        background: #00ff00;\n        position: fixed;\n        top: 50px;\n      }\n      \n      .main-header, .sub-header {\n        transition: transform .15s ease-in-out;\n        transform: translateY(0px);\n      }\n      \n      .main-header.hidden, .sub-header.hidden {\n        transform: translateY(-50px);\n      }\n    </style>\n```\n\n[查看示例](https://sfc.vuejs.org/#eNqVlMtu1DAUhl/lEISmSJNLJbognaKy4xWQskliZ+JOfJHtTFuNZg0CsWTDkhU7Vqx4nlY8BsdJPJ1bWnWkkezj/3znt33iVfBeqWjZ0iANZqbUTFkw1LbqXSYAGFdSW1iBphWsodKSwwTFk3O36v6lFMYCMx8YIVTAhVOeVHlj6GvUzOIeiTCcWMpVk1vaoQFmhC2hbHJjLrKA50yENc0J1eG1zpWiOgsG4bg0CyD14dWk7jxMUu9mjYRZjKm+4Ga8wzNtMVZ5RPecsntFl2ElNaYxkBWcJolDLeiti2Da/Z+v/35/v//74+7Lr63EorVWCjD2tqGoVNIwy6RIK3ZDyXkhcZWnSFM356DZvLbDBOGXZcPKhcM/XNELP97s8+7zp/tvP3u7fTFcmMVbF4bTrnyXUFveTKGQ5BZWPYDnes5ECknXGAAqJ4SJuQ+su1YY8j0KTCkVJR0xOnL/nl3Tfktn3ZZ6Wt9821mjaoBrRmztjiR5NUSKvFzMtWwFSeFlVSX488b92UJ/uH3USrW1laH4Yd/se3hz6PghaVT8pOEkcZafNvz4gU2PmbE6F57XjbFbOUSnZwZobmiI2bK1vogXDFrXKh9PsKj79EeqRv3XslN8iO14OMCGbjcbsO+mYBr0b1TIcxVdGSnwHes42PHdgsmC1JOzAN8uN8+C2lpl0jg2VelevysTST2PcRTpVljGaUQNDwstrw0avELKdIsRY3CJ966pQP/uQRhn7knHuZeY0xpk17mmZBfZCrWYR6Xk8a7q8m2UREnMkH0T8aO4Uuq9PR/CnOYRFKpCQjm+UMcxfj1uWLENGC5rHaz/A/dWIp8=)\n\n使用选择器`.main-header.hidden`和`.sub-header.hidden`来选择具有hidden类名的header元素,将它们的transform属性设置为`translateY(-50px)`(即向上偏移50px)。同时为元素设置transition,提供缓动效果。\n\n这样,header的显示和隐藏就实现了,接下来就是检测滚动方向。\n\n## 实现\n\n使用vueUse的`useScroll`和`useDebounceFn`简化了部分代码,\n\n```html\n    <script setup>\n      import { ref, watch } from 'vue';\n      import { useScroll, useDebounceFn } from '@vueuse/core'\n      \n      const isHidden = ref(false);\n      \n      const { directions, isScrolling, arrivedState } = useScroll(document)\n      \n      const checkHeaderStatus = useDebounceFn(\n        (top, bottom, topArrived) => {\n          if (topArrived) {\n            isHidden.value = false\n            return\n          }\n          if (top) {\n            isHidden.value = false\n          } else if (bottom) {\n            isHidden.value = true\n          }\n        },\n        100\n      )\n      watch(directions, () => {\n        if (isScrolling.value) {\n          checkHeaderStatus(directions.top, directions.bottom, arrivedState.top)\n        }\n      })\n    </script>\n```\n\nuseScroll能够输出响应式的滚动方向、滚动状态(到顶部/到底部)和是否在滚动\n\n其中,函数返回的 directions 是一个 reactive 对象,我们对其进行监听,当该变量发生变化时,说明滚动方向发生了改变。\n\n为了防止高频触发(例如反复上下滚动)导致header乱动,使用防抖函数限制了一下,其中`checkHeaderStatus`函数内的\n\n```javascript\n    if (top) {\n      isHidden.value = false\n    } else if (bottom) {\n      isHidden.value = true\n    }\n```\n\n是关键,当向上滚动时,显示header;向下滚动时,隐藏header。\n\n你可能会发现在这段代码的上面,还有一段不明所以的代码:\n\n```javascript\n    if (topArrived) {\n      isHidden.value = false\n      return\n    }\n```\n\n当滚动条到顶部时,直接显示header,这是为了避免iOS设备中safari浏览器橡皮筋效果导致的误判\n\n> 所以为什么 safari 橡皮筋效果也会触发scroll事件啊啊啊啊啊!!!甚至y值能到负值\n\n[查看示例](https://sfc.vuejs.org/#eNqVVUtu2zAQvcpARWEH0C9As6hqBwlQFNlnVcAbWqQsxpIokJSTwNAF2nU3PUF3XXXV8zToMTKkRFv+JUiAwPy8z3BmKK6967oOVw3zEm+iUslrDYrppr6cVQC8rIXUsAbJMh/uiU5zaCGTooQRckafdkCNYrepFEXhm+FnNhdNlbIv1YZyhRzciVIh2chQzX8qKqWBqxtOKatgarzGGSkUO7PyW8waKJcs1RxnPjI6M14tfCBS8hWjt5pohnbTbSxjKtKmZJU+2xVLc5YubxihTBpWozrSNuqxwQKMtah9mAutRekDTq47qzOYXsK6w2AOMgvc7G02cKs/WbgiRcPQxZ5tuy8x27Jy83ZP8Q1SLTCcWWIX7stcLZsttRu0fvd7HsdmYDNmiz4eJn48PLpxG1SiUx8YH6R5IBXa1A7mLsvDahqQDaSPssXJJOo6FXsUJ5qVdYFI27EAE8pXkBZEqenMKwmvgtzaB/eS1DWTM68HnobOPEjc8nqU27yNEpfBFhUmEVKd4Wa8o6ea+SnnE7i32O6ZroJMSKRxEJktH0ot2aNZQdrTn+//f/94+vvz37dfjjiJBnnDqdKPRZfCXJd4g+eCProylkQueJVAbC8kQE0oxWK7BawLVqTnOylQqagZtYrhkTI47ZzxRa4TuIjrB6fW3dMh6yQaG5RTnSfm0O/7lTlJlwuJ15gm8C7LYvxzgQvFTaslkPEHRvtV7LDBUXrzw/Ltx/DhMOIt6ST41YDj2IT8esAvJ8w/FoyWpHJ6doxNU0J4fqGAEcUCZItGOxMH6LGmVb6O0bT7LB91Dbum3THv13ZiOJANzGk2wq6bPN/rHpegJHV4p0SFr5TVwb62G2rmJU555uHzYuYzL9e6VkkUqSw1b9udCoVcRDgKZVNpXrKQqTKYS3GvMMA7VOm/fFYjwsUV1l2yCuM39/K05h70tK57+1ROJKO7kk1VLxdhKspoF3X1MYzDOOKo/RCWR+XMU/qamMG8IIWogLISPxTHZdx+VPD5UKAvVuu1zzkluhY=)\n\n这里还有一个问题,只检测了滚动到顶部,而没有检测滚动到底部。这是因为我没想到什么很好的方法去检测。\n\n前者,可以通过判断y值是否为0(或小于0)来实现;而后者,正常来说,滚动到底部时,文档高度-视窗高度应该和滚动条的y值是一致的。**但是在safari里可能不一致**,在safari中,当地址栏收缩时,上文的公式成立;但是当地址栏是展开状态时,两者会一个相差地址栏的高度。而地址栏是否展开我没找到判断的方法。\n\n如果有知道如何判断是否滚动到底部的,希望能够和我分享分享",
  "attributes": [
    {
      "value": "hidable-header",
      "trait_type": "xlog_slug"
    }
  ]
}