useStickyHeader.ts 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { useScroll } from '@vueuse/core'
  3. import { watch, ref, computed } from 'vue'
  4. // eslint-disable-next-line import/no-restricted-paths
  5. import type LayoutHeader from '#mobile/components/layout/LayoutHeader.vue'
  6. import type { CSSProperties, WatchSource } from 'vue'
  7. export const useStickyHeader = (
  8. dependencies: WatchSource[] = [],
  9. header = ref<InstanceType<typeof LayoutHeader> | HTMLElement>(),
  10. ) => {
  11. const { y, directions } = useScroll(window.document, {
  12. eventListenerOptions: { passive: true },
  13. })
  14. const stickyStyles = ref<{ header?: CSSProperties; body?: CSSProperties }>({})
  15. const headerElement = computed({
  16. get: () => {
  17. if (!header.value) return null
  18. return 'clientHeight' in header.value ? header.value : header.value?.$el
  19. },
  20. set: (value) => {
  21. header.value = value
  22. },
  23. })
  24. watch(
  25. [y, ...dependencies],
  26. () => {
  27. if (!header.value) {
  28. stickyStyles.value = {}
  29. return
  30. }
  31. const height = headerElement.value?.clientHeight || directions.top
  32. const show = y.value <= height
  33. stickyStyles.value = {
  34. header: {
  35. left: '0',
  36. right: '0',
  37. top: '0',
  38. zIndex: 9,
  39. position: 'fixed',
  40. transform: `translateY(${show ? '0px' : '-100%'})`,
  41. transition: 'transform 0.3s ease-in-out',
  42. },
  43. body: {
  44. marginTop: `${height}px`,
  45. },
  46. } as const
  47. },
  48. { flush: 'post' },
  49. )
  50. return {
  51. stickyStyles,
  52. headerElement,
  53. }
  54. }