css 滚动条导致页面偏移问题
问题描述
水平居中的元素因为纵向滚动条的出现,导致位置出现了偏移
问题重现
水平居中元素因为横向滚动条的出现和隐藏产生偏移(点击蓝色按钮可以看到效果)
水平居中通过 margin:0 auto;
实现
下面是详情
<script setup lang="ts">
import { ref } from 'vue'
const rows = ref<number>(0)
const onClick = () => rows.value = rows.value === 0 ? 10 : 0
</script>
<template>
<div class="w-400px mx-auto h-20px bg-pink-400" />
<div class="h-200px overflow-auto">
<!-- 设置 margin-left: auto; margin-right: auto; -->
<div class="w-400px mx-auto bg-blue-300">
<button class="succeed" @click="onClick">
{{ rows !== 0 ? '恢复原状' : '加载更多' }}
</button>
<div class="text-lg py-2">
下面是详情
</div>
<li v-for="idx in rows" :key="idx">
{{ idx + 1 }} 这只伶俐的棕色狐狸跳过一只懒惰的狗
</li>
</div>
</div>
</template>
水平居中通过 display: flex;justify-content: center;
实现
下面是详情
<script setup lang="ts">
import { ref } from 'vue'
const rows = ref<number>(0)
const onClick = () => rows.value = rows.value === 0 ? 10 : 0
</script>
<template>
<div class="w-400px mx-auto h-20px bg-pink-400" />
<!-- 设置 flex 布局 和 justify-content: center; -->
<div class="h-200px overflow-auto flex justify-center ">
<div class="w-400px mx-auto bg-blue-300">
<button class="succeed" @click="onClick">
{{ rows !== 0 ? '恢复原状' : '加载更多' }}
</button>
<div class="text-lg py-2">
下面是详情
</div>
<li v-for="idx in rows" :key="idx">
{{ idx + 1 }} 这只伶俐的棕色狐狸跳过一只懒惰的狗
</li>
</div>
</div>
</template>
问题原因
- 上面的居中是相当于视口的居中,而
win
的滚动条是需要占据视口宽度的(mac
滚动条默认不占据视口宽度)。 - 因此当滚动条出现,那么视口就变窄了,那么原来居中元素的位置也需要重新调整
问题解决
1. 动态计算 margin-left
vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
const rows = ref<number>(0)
const onClick = () => rows.value = rows.value === 0 ? 10 : 0
const innerRef = ref<HTMLDivElement>()
const resizeObserver = new ResizeObserver(() => {
if (!innerRef.value) return
const { parentElement, offsetWidth } = innerRef.value
const { offsetWidth: parentOffsetWidth } = parentElement as HTMLDivElement
const marginLeft = (parentOffsetWidth - offsetWidth) / 2
Object.assign(innerRef.value.style, { marginLeft: `${marginLeft}px` })
})
function onChangeWidth() {
if (!innerRef.value) return
const { offsetWidth } = innerRef.value
if (offsetWidth === 300)
Object.assign(innerRef.value.style, { width: '200px' })
else
Object.assign(innerRef.value.style, { width: '300px' })
}
onMounted(() => {
if (!innerRef.value) return
resizeObserver.observe(innerRef.value)
})
</script>
<template>
<div class="w-400px mx-auto h-20px bg-pink-400" />
<div class="h-200px overflow-auto">
<div ref="innerRef" class="w-400px mx-auto bg-blue-300">
<div>
<button class="succeed" @click="onClick">
{{ rows !== 0 ? '恢复原状' : '加载更多' }}
</button>
</div>
<div>
<button class="succeed" @click="onChangeWidth">
改变居中元素宽度
</button>
</div>
<div class="text-lg py-2">
下面是详情
</div>
<li v-for="idx in rows" :key="idx">
{{ idx + 1 }} 这只伶俐的棕色狐狸跳过一只懒惰的狗
</li>
</div>
</div>
</template>
2. 动态计算滚动条宽度
vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
const rows = ref<number>(0)
const onClick = () => rows.value = rows.value === 0 ? 10 : 0
const containerRef = ref<HTMLDivElement>()
const innerRef = ref<HTMLDivElement>()
const resizeObserver = new ResizeObserver(() => {
if (!containerRef.value || !innerRef.value) return
const { offsetWidth, clientWidth } = containerRef.value
/* 计算滚动条的宽度 */
const scrollBarWidth = offsetWidth - clientWidth
Object.assign(containerRef.value.style, { paddingLeft: `${scrollBarWidth}px` })
})
function onChangeWidth() {
if (!innerRef.value) return
const { offsetWidth } = innerRef.value
if (offsetWidth === 300)
Object.assign(innerRef.value.style, { width: '200px' })
else
Object.assign(innerRef.value.style, { width: '300px' })
}
onMounted(() => {
if (!containerRef.value) return
resizeObserver.observe(containerRef.value)
})
</script>
<template>
<div class="w-400px mx-auto h-20px bg-pink-400" />
<div ef="containerRef" class="h-200px overflow-auto flex justify-center">
<div ref="innerRef" class="w-400px mx-auto bg-blue-300">
<div>
<button class="succeed" @click="onClick">
{{ rows !== 0 ? '恢复原状' : '加载更多' }}
</button>
</div>
<div>
<button class="succeed" @click="onChangeWidth">
改变居中元素宽度
</button>
</div>
<div class="text-lg py-2">
下面是详情
</div>
<li v-for="idx in rows" :key="idx">
{{ idx + 1 }} 这只伶俐的棕色狐狸跳过一只懒惰的狗
</li>
</div>
</div>
</template>
3.不使用浏览器默认滚动条
如:采用 el-scroll 这种 js
模拟浏览器滚动条的方案