|
这篇文章主要介绍了如何通过vue实现微博中常见的@人的功能,同时增加鼠标点击事件和一些页面小优化。感兴趣的小伙伴可以跟随小编一起学习一下
本文采用vue,同时增加鼠标点击事件和一些页面小优化


基本结构
新建一个sandBox.vue文件编写功能的基本结构- ‹div class="content">
- ‹!--文本框-->
- ‹div
- class="editor"
- ref="divRef"
- contenteditable
- @keyup="handkeKeyUp"
- @keydown="handleKeyDown"
- >‹/div>
- ‹!--选项-->
- ‹AtDialog
- v-if="showDialog"
- :visible="showDialog"
- :position="position"
- :queryString="queryString"
- @onPickUser="handlePickUser"
- @onHide="handleHide"
- @onShow="handleShow"
- >‹/AtDialog>
- ‹/div>
- ‹script>
- import AtDialog from '../components/AtDialog'
- export default {
- name: 'sandBox',
- components: { AtDialog },
- data () {
- return {
- node: '', // 获取到节点
- user: '', // 选中项的内容
- endIndex: '', // 光标最后停留位置
- queryString: '', // 搜索值
- showDialog: false, // 是否显示弹窗
- position: {
- x: 0,
- y: 0
- }// 弹窗显示位置
- }
- },
- methods: {
- // 获取光标位置
- getCursorIndex () {
- const selection = window.getSelection()
- return selection.focusOffset // 选择开始处 focusNode 的偏移量
- },
- // 获取节点
- getRangeNode () {
- const selection = window.getSelection()
- return selection.focusNode // 选择的结束节点
- },
- // 弹窗出现的位置
- getRangeRect () {
- const selection = window.getSelection()
- const range = selection.getRangeAt(0) // 是用于管理选择范围的通用对象
- const rect = range.getClientRects()[0] // 择一些文本并将获得所选文本的范围
- const LINE_HEIGHT = 30
- return {
- x: rect.x,
- y: rect.y + LINE_HEIGHT
- }
- },
- // 是否展示 @
- showAt () {
- const node = this.getRangeNode()
- if (!node || node.nodeType !== Node.TEXT_NODE) return false
- const content = node.textContent || ''
- const regx = /@([^@\s]*)$/
- const match = regx.exec(content.slice(0, this.getCursorIndex()))
- return match && match.length === 2
- },
- // 获取 @ 用户
- getAtUser () {
- const content = this.getRangeNode().textContent || ''
- const regx = /@([^@\s]*)$/
- const match = regx.exec(content.slice(0, this.getCursorIndex()))
- if (match && match.length === 2) {
- return match[1]
- }
- return undefined
- },
- // 创建标签
- createAtButton (user) {
- const btn = document.createElement('span')
- btn.style.display = 'inline-block'
- btn.dataset.user = JSON.stringify(user)
- btn.className = 'at-button'
- btn.contentEditable = 'false'
- btn.textContent = `@${user.name}`
- const wrapper = document.createElement('span')
- wrapper.style.display = 'inline-block'
- wrapper.contentEditable = 'false'
- const spaceElem = document.createElement('span')
- spaceElem.style.whiteSpace = 'pre'
- spaceElem.textContent = '\u200b'
- spaceElem.contentEditable = 'false'
- const clonedSpaceElem = spaceElem.cloneNode(true)
- wrapper.appendChild(spaceElem)
- wrapper.appendChild(btn)
- wrapper.appendChild(clonedSpaceElem)
- return wrapper
- },
- replaceString (raw, replacer) {
- return raw.replace(/@([^@\s]*)$/, replacer)
- },
- // 插入@标签
- replaceAtUser (user) {
- const node = this.node
- if (node && user) {
- const content = node.textContent || ''
- const endIndex = this.endIndex
- const preSlice = this.replaceString(content.slice(0, endIndex), '')
- const restSlice = content.slice(endIndex)
- const parentNode = node.parentNode
- const nextNode = node.nextSibling
- const previousTextNode = new Text(preSlice)
- const nextTextNode = new Text('\u200b' + restSlice) // 添加 0 宽字符
- const atButton = this.createAtButton(user)
- parentNode.removeChild(node)
- // 插在文本框中
- if (nextNode) {
- parentNode.insertBefore(previousTextNode, nextNode)
- parentNode.insertBefore(atButton, nextNode)
- parentNode.insertBefore(nextTextNode, nextNode)
- } else {
- parentNode.appendChild(previousTextNode)
- parentNode.appendChild(atButton)
- parentNode.appendChild(nextTextNode)
- }
- // 重置光标的位置
- const range = new Range()
- const selection = window.getSelection()
- range.setStart(nextTextNode, 0)
- range.setEnd(nextTextNode, 0)
- selection.removeAllRanges()
- selection.addRange(range)
- }
- },
- // 键盘抬起事件
- handkeKeyUp () {
- if (this.showAt()) {
- const node = this.getRangeNode()
- const endIndex = this.getCursorIndex()
- this.node = node
- this.endIndex = endIndex
- this.position = this.getRangeRect()
- this.queryString = this.getAtUser() || ''
- this.showDialog = true
- } else {
- this.showDialog = false
- }
- },
- // 键盘按下事件
- handleKeyDown (e) {
- if (this.showDialog) {
- if (e.code === 'ArrowUp' ||
- e.code === 'ArrowDown' ||
- e.code === 'Enter') {
- e.preventDefault()
- }
- }
- },
- // 插入标签后隐藏选择框
- handlePickUser (user) {
- this.replaceAtUser(user)
- this.user = user
- this.showDialog = false
- },
- // 隐藏选择框
- handleHide () {
- this.showDialog = false
- },
- // 显示选择框
- handleShow () {
- this.showDialog = true
- }
- }
- }
- ‹/script>
-
- ‹style scoped lang="scss">
- .content {
- font-family: sans-serif;
- h1{
- text-align: center;
- }
- }
- .editor {
- margin: 0 auto;
- width: 600px;
- height: 150px;
- background: #fff;
- border: 1px solid blue;
- border-radius: 5px;
- text-align: left;
- padding: 10px;
- overflow: auto;
- line-height: 30px;
- &:focus {
- outline: none;
- }
- }
- ‹/style>
复制代码 如果添加了点击事件,节点和光标位置获取,需要在【键盘抬起事件】中获取,并保存到data- // 键盘抬起事件
- handkeKeyUp () {
- if (this.showAt()) {
- const node = this.getRangeNode() // 获取节点
- const endIndex = this.getCursorIndex() // 获取光标位置
- this.node = node
- this.endIndex = endIndex
- this.position = this.getRangeRect()
- this.queryString = this.getAtUser() || ''
- this.showDialog = true
- } else {
- this.showDialog = false
- }
- },
复制代码 新建一个组件,编辑弹窗选项 - ‹template>
- ‹div
- class="wrapper"
- :style="{position:'fixed',top:position.y +'px',left:position.x+'px'}">
- ‹div v-if="!mockList.length" class="empty">无搜索结果‹/div>
- ‹div
- v-for="(item,i) in mockList"
- :key="item.id"
- class="item"
- :class="{'active': i === index}"
- ref="usersRef"
- @click="clickAt($event,item)"
- @mouseenter="hoverAt(i)"
- >
- ‹div class="name">{{item.name}}‹/div>
- ‹/div>
- ‹/div>
- ‹/template>
-
- ‹script>
- const mockData = [
- { name: 'HTML', id: 'HTML' },
- { name: 'CSS', id: 'CSS' },
- { name: 'Java', id: 'Java' },
- { name: 'JavaScript', id: 'JavaScript' }
- ]
- export default {
- name: 'AtDialog',
- props: {
- visible: Boolean,
- position: Object,
- queryString: String
- },
- data () {
- return {
- users: [],
- index: -1,
- mockList: mockData
- }
- },
- watch: {
- queryString (val) {
- val ? this.mockList = mockData.filter(({ name }) => name.startsWith(val)) : this.mockList = mockData.slice(0)
- }
- },
- mounted () {
- document.addEventListener('keyup', this.keyDownHandler)
- },
- destroyed () {
- document.removeEventListener('keyup', this.keyDownHandler)
- },
- methods: {
- keyDownHandler (e) {
- if (e.code === 'Escape') {
- this.$emit('onHide')
- return
- }
- // 键盘按下 => ↓
- if (e.code === 'ArrowDown') {
- if (this.index >= this.mockList.length - 1) {
- this.index = 0
- } else {
- this.index = this.index + 1
- }
- }
- // 键盘按下 => ↑
- if (e.code === 'ArrowUp') {
- if (this.index ‹= 0) {
- this.index = this.mockList.length - 1
- } else {
- this.index = this.index - 1
- }
- }
- // 键盘按下 => 回车
- if (e.code === 'Enter') {
- if (this.mockList.length) {
- const user = {
- name: this.mockList[this.index].name,
- id: this.mockList[this.index].id
- }
- this.$emit('onPickUser', user)
- this.index = -1
- }
- }
- },
- clickAt (e, item) {
- const user = {
- name: item.name,
- id: item.id
- }
- this.$emit('onPickUser', user)
- this.index = -1
- },
- hoverAt (index) {
- this.index = index
- }
- }
- }
- ‹/script>
-
- ‹style scoped lang="scss">
- .wrapper {
- width: 238px;
- border: 1px solid #e4e7ed;
- border-radius: 4px;
- background-color: #fff;
- box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
- box-sizing: border-box;
- padding: 6px 0;
- }
- .empty{
- font-size: 14px;
- padding: 0 20px;
- color: #999;
- }
- .item {
- font-size: 14px;
- padding: 0 20px;
- line-height: 34px;
- cursor: pointer;
- color: #606266;
- &.active {
- background: #f5f7fa;
- color: blue;
- .id {
- color: blue;
- }
- }
- &:first-child {
- border-radius: 5px 5px 0 0;
- }
- &:last-child {
- border-radius: 0 0 5px 5px;
- }
- .id {
- font-size: 12px;
- color: rgb(83, 81, 81);
- }
- }
- ‹/style>
复制代码 以上就是如何通过Vue实现@人的功能的详细内容,更多关于Vue @人功能的资料请关注脚本之家其它相关文章!

|
|