效果预览
适用场景
可编辑的顶部标题
使用组件
- el-container
- el-header
- el-input
起始组件内容
vue
<template>
<div class="common-layout">
<el-container class="container">
<el-header class="header">
<el-input v-model="title" />
</el-header>
</el-container>
</div>
</template>
<script setup>
import { ref } from 'vue'
const title = ref('这是标题')
</script>
<style scoped></style>
起始的效果
虽然输入框只有四个字"这是标题", 但输入框的宽度几乎占满了浏览器的宽度
现在输入框的宽度几乎占满浏览器的宽度, 我们要做的是输入框的宽度随着字数的增加而增加, 所以, 我们先给输入框设置一个初始的合适的宽度,
vue
<el-input style="width: 6em" v-model="title" />
再让el-input居中
ini
<el-header class="header">
css
.header {
display: flex;
place-content: center;
}
输入框长度随着文字增加而增加
这个肯定要监听输入框的事件, 那么怎么获取输入框的引用呢?
获取输入框的引用
模板引用 肯定用不了, 因为el-input里面还是个组件, 所以, 我选择用原始的js代码
ini
// <el-input class="title" style="width: 6em" v-model="title" />
var inner_input = document.querySelector('.title .el-input__inner')
监听输入框事件
javascript
inner_input.addEventListener('input', function () {
...
})
现在可以监听输入框事件了, 我们要输入文字, 然后改变输入框的宽度
改变输入框宽度
ini
// <el-input class="title" :style="`width:${titleInputWidth}em`" v-model="title" />
onMounted(() => {
var inner_input = document.querySelector('.title .el-input__inner')
inner_input.addEventListener('input', function () {
titleInputWidth.value = this.value.length
})
})
这个时候的输入框就, 就可以随着输入文字, 输入框的宽度就随之改变.
输入框最小宽度
当我们把输入框的文字清空之后, 发现输入框变的很狭窄
设置最小宽度
css
// <el-input class="title" :style="`width:${titleInputWidth}em`" v-model="title" />
.title {
min-width: 6em;
}
增加一个汉字, 宽度应该增加多少?
下面这个是"这是标题这是标题"
可以看到, 最后的两个字"标题"看不见了, 所以, 增加一个汉字, 只增加1em就不行, 那么应该加多少呢?
一个字符宽度多少?
我们把中英文数字都测一下, 可以得到准确的宽度, 当然了, 这种宽度仅限于当前被测的字符.
xml
<template>
<div class="common-layout">
<el-container class="container">
<el-header class="header">
<div class="outer-container">
<el-input class="title" :style="`width:${titleInputWidth}px`" v-model="title" />
<div class="inner-container">
<span ref="measureRefHan" :style="{ visibility: 'hidden', position: 'absolute' }">{{
testCharHan
}}</span>
<span ref="measureRefLetter" :style="{ visibility: 'hidden', position: 'absolute' }">{{
testCharLetter
}}</span>
<span ref="measureRefNumber" :style="{ visibility: 'hidden', position: 'absolute' }">{{
testCharNumber
}}</span>
</div>
</div>
</el-header>
</el-container>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const title = ref('这是标题')
const titleInputWidth = ref(6)
const measureRefHan = ref(null)
const measureRefLetter = ref(null)
const measureRefNumber = ref(null)
const testCharHan = '汉'
const testCharLetter = 'a'
const testCharNumber = '0'
// 定义函数来判断字符类型
function isChineseCharacter(char) {
return /[\u4e00-\u9fff]/.test(char)
}
function isEnglishOrDigit(char) {
return /[a-zA-Z0-9]/.test(char)
}
onMounted(() => {
const hanCharWidth = measureRefHan.value.offsetWidth
const letterCharWidth = measureRefLetter.value.offsetWidth
const numberCharWidth = measureRefNumber.value.offsetWidth
console.log(`The width of the character "${testCharHan}" is ${hanCharWidth}px.`)
console.log(`The width of the character "${testCharLetter}" is ${letterCharWidth}px.`)
console.log(`The width of the character "${testCharNumber}" is ${numberCharWidth}px.`)
var inner_input = document.querySelector('.title .el-input__inner')
inner_input.addEventListener('input', function () {
let currentWidth = 0
for (let i = 0; i < this.value.length; i++) {
const char = this.value[i]
if (isChineseCharacter(char)) {
currentWidth += hanCharWidth
} else if (isEnglishOrDigit(char)) {
currentWidth += letterCharWidth
}
}
titleInputWidth.value = currentWidth
})
})
</script>
<style scoped>
.header {
display: flex;
place-content: center;
}
.title {
min-width: 6em;
}
.outer-container {
background-color: aqua;
width: 100%;
display: flex;
place-content: center;
}
.inner-container {
display: flex;
}
</style>
可以看到, 我打了个很多个1111111, 但是数字1和0的宽度明显不一样, 而且最不顺眼的是输入框的文字不居中,
那么, 我们先把居中搞上
输入框文字居中
css
:deep(.title .el-input__inner) {
text-align: center;
}
输入框文字居中了, 但是字符宽度还没解决, 不过作为一个可修改的标题组件, 基本够用了. 对于能用就行的人来说, 就可以不用往下看了.
自适应宽度的组件有哪些?
尤其是针对文字的, 常用的不是p标签, 就是span标签, 我们就用span标签来测量字符串的正确宽度
下面这个span调试的时候是可见的, 调试完以后就改为不可见 visibility: 'hidden'
xml
<template>
<div class="common-layout">
<el-container class="container">
<el-header class="header">
<div class="outer-container">
<el-input class="title" :style="`width:${titleInputWidth}px`" v-model="title" />
<div class="inner-container">
<span ref="measureSpanRef" :style="{ visibility: 'visible', position: 'absolute' }">{{
title
}}</span>
</div>
</div>
</el-header>
</el-container>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const title = ref('这是标题')
const titleInputWidth = ref(0)
const measureSpanRef = ref(null)
onMounted(() => {
const inner_input = document.querySelector('.title .el-input__inner')
inner_input.addEventListener('input', function () {
var offsetWidth = measureSpanRef.value.offsetWidth
titleInputWidth.value = offsetWidth
})
})
</script>
<style scoped>
.header {
display: flex;
place-content: center;
}
.title {
min-width: 6em;
place-content: center;
}
.outer-container {
position: relative;
width: 100%;
background-color: aqua;
width: 100%;
display: flex;
place-content: center;
}
.inner-container {
display: flex;
place-content: center;
position: absolute; /* 绝对定位 */
width: 100%; /* 容器宽度占满整个网页 */
}
:deep(.title .el-input__inner) {
text-align: center;
width: auto;
}
</style>
可以看到, span和input的字体大小不一样, 所以我们要把两个组件的字体大小设置一样,
css
.measure-span {
font-size: 16px;
font-family: Arial, sans-serif;
font-weight: normal;
font-style: normal;
letter-spacing: normal;
white-space: nowrap;
}
这下在只有一行文字的情况下, 可以保证span和input的文字宽度是一样的啦, 也不用自己去测量每个字符的宽度啦, 直接使用span的宽度.
贴一下最终的代码
vue
<template>
<div class="common-layout">
<el-container class="container">
<el-header class="header">
<div class="outer-container">
<el-input class="title" :style="`width:${titleInputWidth}px`" v-model="title" />
<div class="inner-container">
<span
ref="measureSpanRef"
class="measure-span"
:style="{ visibility: 'hidden', position: 'absolute' }"
>{{ title }}</span
>
</div>
</div>
</el-header>
</el-container>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const title = ref('这是标题')
const titleInputWidth = ref(0)
const measureSpanRef = ref(null)
onMounted(() => {
const inner_input = document.querySelector('.title .el-input__inner')
inner_input.addEventListener('input', function () {
var offsetWidth = measureSpanRef.value.offsetWidth
titleInputWidth.value = offsetWidth + 32
})
})
</script>
<style scoped>
.header {
display: flex;
place-content: center;
}
.title {
min-width: 6em;
place-content: center;
}
.outer-container {
position: relative;
width: 100%;
background-color: aqua;
width: 100%;
display: flex;
place-content: center;
}
.inner-container {
display: flex;
place-content: center;
position: absolute; /* 绝对定位 */
width: 100%;
}
:deep(.title .el-input__inner) {
text-align: center;
width: auto;
font-size: 16px;
font-family: Arial, sans-serif;
font-weight: normal;
font-style: normal;
letter-spacing: normal;
white-space: nowrap;
}
.measure-span {
font-size: 16px;
font-family: Arial, sans-serif;
font-weight: normal;
font-style: normal;
letter-spacing: normal;
white-space: nowrap;
}
</style>
titleInputWidth.value = offsetWidth + 32 加32是因为input的父组件有padding, 32抵消了padding, 使得字符串左右保留了一小段空白, 让人能看到字符串的头尾.
知识点
- 上下层组件用绝对定位, 就像span一样, span在input上层.
- deep深层选择器, vue3的穿透