基于vue3實(shí)現(xiàn)一個簡單的輸入框效果
需求背景
需要一個輸入框,可以輸入文字,添加表情,一開始用了富文本編輯器,有點(diǎn)大材小用,所以自己封裝一個輸入框組件。支持輸入文字,選擇表情/插入表情,支持組合鍵換行,使用enter 進(jìn)行提交
效果圖

技術(shù)實(shí)現(xiàn)
- 通過原生
textarea實(shí)現(xiàn) - 通過
v-model來實(shí)現(xiàn) 父子組件的數(shù)據(jù)傳遞,子組件監(jiān)聽數(shù)據(jù)的變化,向外emit('update:modelValue', inputValue),保證父組件能更新綁定的值 - 每次插入時,需要重新聚焦,更新光標(biāo)位置
- 通過向外 暴露的 (__insertText),來實(shí)現(xiàn)插入表情/文字
- 通過向外暴露的(_clear) 輸入完畢發(fā)送后,需要
clear掉輸入框的內(nèi)容 - 通過向外暴露的(__isEmpty) 來判斷是否有內(nèi)容,如果沒內(nèi)容,做按鈕的禁用狀態(tài)(當(dāng)然也可以直接用父組件綁定的值)
- 父組件通過
ref就可以調(diào)用以上方法,來做操作。比如發(fā)送完數(shù)據(jù),調(diào)clear清空內(nèi)容,如果輸入框沒有內(nèi)容,則調(diào)用isEmpty,做按鈕的一些狀態(tài)
代碼實(shí)現(xiàn)(子組件)
/**
* 自定義文本輸入框組件
*/
import { ref, watch } from 'vue'
import { ElInput } from 'element-plus'
export default defineComponent({
props: {
modelValue: {
type: String,
default: ''
}
},
emits: ['update:modelValue', 'chatSend'],
setup(props, { emit, expose }) {
const { t } = useI18n()
const editorRef = ref()
const disabled = ref(false)
const valueHtml = ref(props.modelValue)
const currentEvent = ref()
watch(
() => valueHtml.value,
() => {
emit('update:modelValue', valueHtml.value)
}
)
// 插入元素
const _insertText = async (msg: string) => {
const msgLength = msg.length
const editor = currentEvent.value
if (editor) {
const startPos = editor.selectionStart
valueHtml.value = `${valueHtml.value.substring(0, startPos)}${'' + msg}${valueHtml.value.substring(startPos)}`
_focus()
nextTick(() => {
// 此處是 根據(jù)元素的長度,來設(shè)置光標(biāo)位置
editor.setSelectionRange(startPos + msgLength, startPos + msgLength)
})
}
}
// focus
const _focus = () => {
editorRef.value && editorRef.value.focus()
}
// 清空編輯器
const _clear = () => {
editorRef.value && editorRef.value.clear()
}
// 是否內(nèi)容為空
const _isEmpty = () => {
const str = valueHtml.value.trim().replace(/\n/g, '')
return str === '' || str.length == 0 || typeof str === 'undefined'
}
// 禁用編輯器
const _disable = () => {
disabled.value = true
}
// 解除禁用編輯器
const _enable = () => {
disabled.value = false
}
const handleKeyDown = (event: KeyboardEvent) => {
currentEvent.value = event.target as HTMLInputElement
// 定義組合鍵 Map
const shortCutKeys: (keyof KeyboardEvent)[] = ['metaKey', 'altKey', 'ctrlKey', 'shiftKey']
const isEnterKey = event.code === 'Enter'
const isShortcutKeys = shortCutKeys.some((item) => event[item])
if (isEnterKey && isShortcutKeys) {
// 獲取光標(biāo)位置
const cursorPosition = currentEvent.value.selectionStart
// 拆分成兩段文本
const textBeforeCursor = valueHtml.value.slice(0, cursorPosition)
const textAfterCursor = valueHtml.value.slice(cursorPosition)
// 合并為帶有換行符的新文本
const newText = textBeforeCursor + '\n' + textAfterCursor
// 更新輸入框的值
valueHtml.value = newText
// 文本編輯器的高度發(fā)生變化后
nextTick(() => {
// 高度變化 自動滾動到底部
const editor = editorRef.value.textarea
editorRef.value.textarea.scrollTop = editor.scrollHeight
// 設(shè)置光標(biāo)位置為: start 和 end 相同,光標(biāo)會移動到換行符后面的新行首
currentEvent.value.setSelectionRange(cursorPosition + 1, cursorPosition + 1)
})
} else if (event.code === 'Enter') {
// 阻止掉 Enter 的默認(rèn)換行行為
event.preventDefault()
emit('chatSend')
}
}
// 向外暴露方法
expose({
_insertText,
_clear,
_disable,
_isEmpty,
_enable,
_focus
})
return () => (
<div class="chatEditor">
<ElInput
ref={editorRef}
v-model={valueHtml.value}
type="textarea"
disabled={disabled.value}
onKeydown={handleKeyDown}
/>
</div>
)
}
})代碼實(shí)現(xiàn)(父組件調(diào)用)
輸入框組件
<base-editor
ref="chatEditorRef"
v-model="inputText"
@chat-send="sendText"
></base-editor>
工具欄組件
<ChatTools :chat-tools-list="newChatToolsList" @get-emoji="getToolsMsg" />
當(dāng)我們點(diǎn)擊工具欄組件,就會獲取到工具欄的文字/表情/或者插入的xxx ,此時根據(jù)引用,調(diào)用輸入框的暴露出的_insertText方法,直接就插入進(jìn)去
const getToolsMsg = async (msg: string) => {
chatEditorRef.value._insertText(msg)
}
其他方法調(diào)用
_isEmpty(): 未輸入,按鈕是禁用狀態(tài)
<el-button :disabled="chatEditorRef?._isEmpty()" type="primary" @click="sendText">
當(dāng)然你也可以直接使用綁定的輸入框的變量去判斷
<el-button :disabled="!inputText" @click="sendText">
_clear(): 提交完請求,清空輸入框
chatEditorRef.value._clear()
關(guān)于插入的問題
光標(biāo)位置的獲取,根據(jù)元素插入位置,光標(biāo)換行,支持快捷鍵等??梢詤⒖忌弦黄恼拢?a href="http://m.fzitv.net/javascript/316756c60.htm" target="_blank">vue3通過組合鍵實(shí)現(xiàn)換行操作的示例詳解
總結(jié)
- 原生其實(shí)也可以實(shí)現(xiàn),沒必要用一個很重的富文本編輯器
- 做表情插入,或者其他插入,都是工具欄,和輸入框組件的關(guān)系是兄弟組件的關(guān)系,兄弟組件之間,怎么做數(shù)據(jù)傳遞,事件傳遞,怎么設(shè)計(jì)?
(1) 父組件作為中介
(2)事件總線通過訂閱/發(fā)布做消息通知
(3)倉庫vuex/pina
事件總線還是能不用就不用,因?yàn)槿中缘臇|西,用著爽,后面復(fù)雜了,就亂了。而且 事件總線是發(fā)布訂閱,你還得注意銷毀,存?zhèn)}庫 又不太合適,子組件自己暴露出方法,讓其他組件調(diào)用,感覺目前是最簡單的方式了。
到此這篇關(guān)于基于vue3實(shí)現(xiàn)一個簡單的輸入框效果的文章就介紹到這了,更多相關(guān)vue3輸入框內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue實(shí)現(xiàn)虛擬列表組件解決長列表性能問題
這篇文章主要介紹了在vue中實(shí)現(xiàn)虛擬列表組件,解決長列表性能問題,本文給大家分享實(shí)現(xiàn)思路及實(shí)例代碼,需要的朋友可以參考下2022-07-07
element?ui設(shè)置table自適應(yīng)表格寬,不自動換行問題
這篇文章主要介紹了element?ui設(shè)置table自適應(yīng)表格寬,不自動換行問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
Vue+Element UI實(shí)現(xiàn)概要小彈窗的全過程
彈窗效果是我們?nèi)粘i_發(fā)中經(jīng)常遇到的一個功能,下面這篇文章主要給大家介紹了關(guān)于Vue+Element UI實(shí)現(xiàn)概要小彈窗的相關(guān)資料,需要的朋友可以參考下2021-05-05
如何實(shí)現(xiàn)echarts markline標(biāo)簽名顯示自己想要的
這篇文章主要介紹了實(shí)現(xiàn)echarts markline標(biāo)簽名顯示自己想要的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
vue 封裝 Adminlte3組件的實(shí)現(xiàn)
這篇文章主要介紹了vue 封裝 Adminlte3組件的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
解決vue 使用setTimeout,離開當(dāng)前路由setTimeout未銷毀的問題
這篇文章主要介紹了解決vue 使用setTimeout,離開當(dāng)前路由setTimeout未銷毀的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07

