Kliping
KlipingBerandaDokumentasiKomponenBlocksVariasiTemplateDice UIHooks
Memulai
  • Pengenalan
  • Instalasi
  • Kredit
State
  • useBoolean
  • useControllableValue
  • useCounter
  • useDebounce
  • useResetState
  • useThrottle
  • useToggle
Lanjutan
  • useCreation
  • useCustomCompareEffect
  • useLatest
  • useLockFn
  • useMemoizedFn
  • usePrevious
Siklus Hidup
  • useDebounceEffect
  • useDebounceFn
  • useDeepCompareEffect
  • useDeepCompareLayoutEffect
  • useEffectEvent
  • useEffectWithTarget
  • useInterval
  • useIsHydrated
  • useIsomorphicLayoutEffect
  • useMount
  • useThrottleEffect
  • useThrottleFn
  • useTimeout
  • useUnmount
  • useUpdate
  • useUpdateEffect
Peramban
  • useActiveElement
  • useBattery
  • useClickAnyWhere
  • useClickAway
  • useClipboard
  • useDocumentVisibility
  • useElementSize
  • useEventListener
  • useFps
  • useFullscreen
  • useHash
  • useHover
  • useInViewport
  • useIsMatchMedia
  • useIsOnline
  • useMouse
  • useNetwork
  • useOs
  • useScrollLock
  • useTextSelection
  • useTitle
Pengembangan
  • useWhyDidYouUpdate
Paket Luar
  • useMcp
  • useQuery
  • useStickToBottom
  • useSwr
Pustaka
  • isBrowser
  • createContext
  • bacaElemen
  • saatTerpasang

useScrollLock

SebelumnyaBerikutnya

Mengunci gulungan halaman untuk dialog, drawer, dan menu layar penuh.

Gulungan halaman: bebas

Perhatikan isinya tidak bergeser saat bilah gulung hilang.

Mengunci gulungan halaman — untuk dialog, drawer, dan menu layar penuh.

Bagian yang paling sering dilupakan bukan penguncian, melainkan lebar yang melompat. Menyembunyikan bilah gulung membuat halaman melebar beberapa belas piksel, dan seluruh isinya bergeser tepat saat dialognya muncul. Karena itu lebar bilah gulungnya dihitung lalu dititipkan sebagai padding-right, dan halamannya diam.

Gaya aslinya disimpan sebelum diubah, bukan disetel ke nilai bawaan saat dilepas. Elemen yang sudah punya overflow atau padding-right sendiri mendapatkannya kembali utuh.

Pemasangan ​

npx shadcn-vue@latest add @kliping/use-scroll-lock

Perlu @kliping terdaftar di components.json project Anda. Lihat cara menyiapkannya.

Ikut terpasang sendiri: is-browser, saat-terpasang.

Salin dulu is-browser, saat-terpasang — masing-masing memuat sumbernya di halamannya sendiri — lalu tempel berkas ini.

ts
import { getCurrentScope, onScopeDispose, readonly, ref } from 'vue'
import { isBrowser } from '@/lib/is-browser'
import { saatTerpasang } from '@/lib/saat-terpasang'

export interface UseScrollLockOptions {
  /** Kunci begitu composable-nya dipanggil. Bawaan: true. */
  autoLock?: boolean
  /** Elemen yang dikunci, atau pemilih CSS-nya. Bawaan: `document.body`. */
  lockTarget?: HTMLElement | string
  /** Titipkan lebar bilah gulung sebagai padding supaya isinya tidak bergeser. Bawaan: true. */
  widthReflow?: boolean
}

export function useScrollLock(options: UseScrollLockOptions = {}) {
  const { autoLock = true, lockTarget, widthReflow = true } = options

  const isLocked = ref(false)
  let sasaran: HTMLElement | null = null
  let gayaAsli: { overflow: string, paddingRight: string } | null = null

  const cariSasaran = (): HTMLElement | null => {
    if (!isBrowser)
      return null
    if (typeof lockTarget === 'string')
      return document.querySelector<HTMLElement>(lockTarget)
    return lockTarget ?? document.body
  }

  const lock = () => {
    sasaran ??= cariSasaran()
    if (!sasaran || isLocked.value)
      return

    gayaAsli = {
      overflow: sasaran.style.overflow,
      paddingRight: sasaran.style.paddingRight,
    }

    if (widthReflow) {
      // `document.body` diukur dengan lebar jendela, bukan lebar dirinya:
      // bilah gulung global tidak dihitung sebagai bagian dari dokumen.
      const lebarLuar = sasaran === document.body
        ? window.innerWidth
        : sasaran.offsetWidth

      const paddingKini = Number.parseInt(
        window.getComputedStyle(sasaran).paddingRight,
        10,
      ) || 0

      const lebarBilah = lebarLuar - sasaran.scrollWidth
      sasaran.style.paddingRight = `${lebarBilah + paddingKini}px`
    }

    sasaran.style.overflow = 'hidden'
    isLocked.value = true
  }

  const unlock = () => {
    if (sasaran && gayaAsli) {
      sasaran.style.overflow = gayaAsli.overflow
      if (widthReflow)
        sasaran.style.paddingRight = gayaAsli.paddingRight
    }
    gayaAsli = null
    isLocked.value = false
  }

  saatTerpasang(() => {
    sasaran = cariSasaran()
    if (autoLock)
      lock()
  })

  if (getCurrentScope())
    onScopeDispose(unlock)

  return { isLocked: readonly(isLocked), lock, unlock }
}

API ​

ts
export interface UseScrollLockOptions {
  /** Kunci begitu composable-nya dipanggil. Bawaan: true. */
  autoLock?: boolean
  /** Elemen yang dikunci, atau pemilih CSS-nya. Bawaan: `document.body`. */
  lockTarget?: HTMLElement | string
  /** Titipkan lebar bilah gulung sebagai padding supaya isinya tidak bergeser. Bawaan: true. */
  widthReflow?: boolean
}

export function useScrollLock(options: UseScrollLockOptions = {})
useOsuseTextSelection

Di halaman ini

PemasanganAPI
Dirilis di bawah lisensi MIT. Ditulis ulang untuk Vue dari shadcn-vue, shadcn-ui-blocks, dan shadcn-hooks. Hak cipta © 2026 Kliping