vue中实现div可编辑,并插入指定元素,样式

news2024/11/16 1:24:17

前言:

        vue中实现一个既可以编辑内容,有可以动态编辑内容插入一个带有样式的内容,改变默认内容后,这个样式消失的效果,这里来整理下调研与解决实现问题之路。

实现最终效果:图2为默认内容

1、可以光标点击任意地方,然后点击按钮,插入带有span的内容

2、默认span是有样式,但是一旦内容与我们的默认内容不同就取消样式

3、获取的时候,可以拿到带有标签的内容,也可以拿到纯文本内容

4、默认内容,支持带标签内容

 

 探索之路:

1、刚开始从网上查找了很多资料,但是都有不同的缺陷,下面是我根据网上资料实现的效果

1)封装一个  inputDiv.vue

<template>
  <div
    ref="divInput"
    class="edit-div"
    style="-webkit-user-select: auto"
    :contenteditable="canEdit"
    @blur="onBlur"
    @input="changeText($event)"
    @keyup.space="changeSpace($event)"
    @keyup.enter="changeEnter($event)"
    @paste="onPaste"
    @focus="onFocus"
    placeholder="请输入消息内容"
    slot="title"
    v-html="innerText"
  ></div>


</template>
<script>
export default {
  props: {
    value: {
      type: String,
      default: "",
    },
    canEdit: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      innerText: this.value,
      isLocked: false,
    }
  },
  watch: {
    value() {
      if (!this.isLocked && !this.innerHTML) {
        this.innerText = this.value
      }
    },
  },
  methods: {
    getRefBlur() {
      this.$refs.divInput.blur()
    },
    getRefFocus() {
      this.$refs.divInput.focus()
    },
    onBlur() {
      this.$emit("onblurChange", this.$el.innerHTML)
      this.isLocked = false
    },
    // 主要用于处理光标得定位问题
    keepLastIndex(obj) {
      console.log(obj);
      if (window.getSelection) {
        obj.focus()
        let range = window.getSelection()
        range.selectAllChildren(obj)
        range.collapseToEnd()
      } else if (document.selection) {
        let range = document.selection.createRange() //创建选择对象
        range.moveToElementText(obj) //range定位到obj
        range.collapse(false) //光标移至最后
        range.select()
      }
    },
    changeSpace(e) {
      setTimeout(() => {
        this.keepLastIndex(e.target)
      }, 30)
      this.$emit("keyupSpace", this.$el.innerHTML)
    },
    changeEnter(e) {
      setTimeout(() => {
        this.keepLastIndex(e.target)
      }, 30)
      this.$emit("keyupEnter", this.$el.innerHTML)
    },
    // 实时监听当前内容
    changeText(e) {
      console.log(e)
      if(e.data){
        console.log('数据:'+this.$el.innerHTML)
        setTimeout(() => {
          this.keepLastIndex(e.target)
        }, 30)
        this.$emit("input", this.$el.innerHTML)
      }
    },
    // 输入框粘贴事件
    onPaste(event) {
      this.$emit("onPaste", event)
    },
    //
    onFocus(event) {
      this.$emit("onFocus", event)
    },
  }
}
</script>

<style scoped>

</style>

2)父级中使用:

结果:{{editInputVal}}-{{mrEditInputVal}}
    <el-button @click = 'addStrBtnFun'>{{addBtnText}}</el-button>
    <inputDiv
      ref="imitate"
      id="imitate"
      class="imitate-input imitate-placeholder js-imitate-input"
      @onblurChange="onblurChange($event)"
      @onFocus="getFocus"
      @onPaste="onPaste"
      v-html="mrEditInputVal"
      @input="contentedInput($event)"
      @keyupSpace="contentedKeySpace($event)"
      @keyupEnter="contentedKeyEnter($event)"
      @changeKeyup="changeKeyup($event)"
    ></inputDiv>
js部分:

import inputDiv from './components/inputDiv'
components: {
    inputDiv,
  },
data() {
    return {
      mrEditInputVal:'',
      editInputVal:'',
      addBtnText:'插入员工昵称',
      abc:'',
      teamContent: "<p>幸福 人和 测试增加数据 在加1111222</p>"
    }
  },

methods: {
    onblurChange(val){
      // debugger
    },
    getFocus(val){
      // debugger
    },
    onPaste(val){
      // debugger
    },
    contentedInput(val){
      if(val.indexOf('<span')!= -1){
        let arr1 = val.split('<span')
        let arr2 = arr1[1].split('/span>')
        let arr3 = arr2[0].split('>')
        let arr4 = arr3[1].split('<')
        if(arr4[0] != this.addBtnText){
          let val = this.editInputVal = arr1[0]+arr4[0]+arr2[1]
          this.mrEditInputVal = JSON.parse(JSON.stringify(val))
          return
        }
      }
      this.editInputVal = val
    },
    contentedKeySpace(val){
      // debugger
    },
    contentedKeyEnter(val){
      // debugger
    },
    changeKeyup(val){
      // debugger
    },
    addStrBtnFun(){
      let index = this.getCursorPosition()
      let insertStr = (soure,start, newStr) => {
        return soure.slice(0, start) + newStr + soure.slice(start)
      }
      let newStr = insertStr(this.editInputVal,index,'<span class="selColor">'+this.addBtnText+'</span>')
      this.mrEditInputVal = newStr
    },
    getCursorPosition(){
      const selection = window.getSelection();
      const range = selection.getRangeAt(0);
      const cursorPos = range.startOffset;
      return cursorPos
    },

3)最终实现效果

1、实现了不同内容的时候可以正常监听

2、点击按钮,可以正常插入并修改样式

缺点:

1、点击多个插入,位置有问题

2、每次输入第二个字符的时候,光标都移动到了最后一位

 2、继续努力实现效果,效果图实现步骤:

注意:

        不能实时获取它的数据,它的光标位置会出现问题,目前官方提供的方法,默认是回到第一个,可以修改到回到最后一个,但是我们如果是中间输入内容,光标会异常,故而不建议实时获取数据

1、div+contenteditable="true"  来实现div的可编辑,v-html来实现对他默认值的修改

 <div
      class="edit"
      contenteditable="true"
      @input="editInput"
      placeholder="请输入消息内容"
      v-html="innerHtml"
    >
    </div>

2、初始化方法

// 这一步是保留住edit编辑框中的选区的范围对象。否则失焦后,getSelection()方法返回的选区对象已经不再是编辑框了,已经获取不到编辑框中的范围对象了。

initEditFun(){
      editDiv = document.getElementsByClassName("edit")[0]

      editDiv.addEventListener("blur", () => {
        // 这一步是保留住edit编辑框中的选区的范围对象。否则失焦后,getSelection()方法返回的选区对象已经不再是编辑框了,已经获取不到编辑框中的范围对象了。
        range = window.getSelection().getRangeAt(0)
      })

    },

3、点击按钮时候,给可编辑区域中增加一个带有颜色的span

记得在你的功能样式中添加,可在app.vue,也可以在你项目其他公共区域

.selColor{
  color: red;
}
点击事件:

window.getSelection()  获取他的光标选取

 selection.selectAllChildren(editDiv) // selectAllChildren把指定元素的所有子元素设为选中区域,并取消之前的选中区域。不包括node节点本身。

 selection.collapseToEnd()   //Selection.collapseToEnd() 方法的作用是取消当前选区,并把光标定位在原选区的最末尾处,如果此时光标所处的位置是可编辑的,且它获得了焦点,则光标会在原地闪烁。(想查看更多属性,点击链接,去看官方api)

range = window.getSelection().getRangeAt(0) // 保存当前编辑框的选区对象

 let sel = window.getSelection()
range.insertNode(span)  // insertNode方法,在range选区开头插入一个节点

 sel.removeAllRanges()  //removeAllRanges方法:删除之前的所有选区。
sel.addRange(range)    // 这一步就是添加当前区域对象到选区对象中

addStrBtnFun(){
      const span = document.createElement("span")
      span.innerText = this.addBtnText
      span.className = 'selColor'
      // 如果在页面刷新再点击编辑框之前就点击了按钮,此时range中并没有选区范围对象
      if (range === "") {
        let selection = window.getSelection()
        selection.selectAllChildren(editDiv) // selectAllChildren把指定元素的所有子元素设为选中区域,并取消之前的选中区域。不包括node节点本身。
        /*
          Selection.collapseToEnd() 方法的作用是取消当前选区,并把光标定位在原选区的最末尾处,如果此时光标所处的位置是可编辑的,且它获得了焦点,则光标会在原地闪烁。
          以上selectAllChildren方法,将div中子节点全部选中,collapseToEnd方法将选中区域取消,并且将光标定位到原区的末尾。
        */
        selection.collapseToEnd()
        range = window.getSelection().getRangeAt(0) // 无论哪一步都需要保存当前编辑框的选区对象
      }
      let sel = window.getSelection()
      range.insertNode(span) // insertNode方法,在range选区开头插入一个节点
      /*
      removeAllRanges方法:删除之前的所有选区。
      这一步的意义:因为当我们点击其他区域时,选区对象已经改变,不再是编辑框中的选区对象,这时候我们接下来的操作都不会符合我们想象中的样子
     */
      sel.removeAllRanges()
      sel.addRange(range) // 这一步就是添加当前区域对象到选区对象中,所以选区对象会再次指向编辑框中的选区,不会出现别的错误操作。
      sel.collapseToEnd()
    },

4、我们修改span内容,让他的样式消失方法

1)首先要用到  他的@input事件

@input="editInput"
 editInput(e) {
      console.log(e);
      console.log(e.target.children);
},
这是它的默认拿到的数据 

如果插入span以后,可以看到e.target.children多了span信息

 拿我们页面的edit  div下面所有的span,如果哪个内容被改变,就去掉它的class
editInput(e) {
      console.log(e);
      console.log(e.target.children);
      if(e.target.children.length>0){
        this.editSpanClass()
      }
    },
    editSpanClass(){
      let spanAll = document.querySelectorAll('.edit span')
      spanAll.forEach(item=>{
        if(item.innerHTML != this.addBtnText){
          item.className = ''
        }
      })
    },

源码分享:tinymce.vue

<template>
  <div id="editDivBody">
    <el-button @click = 'addStrBtnFun'>{{addBtnText}}</el-button>
    <el-button @click = 'getNowContent'>获取当前内容</el-button>
    <div
      class="edit"
      contenteditable="true"
      @input="editInput"
      placeholder="请输入消息内容"
      v-html="innerHtml"
    >
    </div>

    {{innerHtml}}
    <hr>
    {{innerText}}
  </div>
</template>

<script>
let editDiv = null //编辑元素
let range = "" //选区
export default {
  data () {
    return {
      addBtnText:'插入员工昵称',
      // 赋值
      innerHtml:'haoxing<h1>111</h1>',
      innerText:'',
      // 获取当前最新数据
      getDataTime:1000,
      getDataTimeInter:null,
    }
  },
  mounted() {
    this.initEditFun()
  },
  methods:{
    initEditFun(){
      editDiv = document.getElementsByClassName("edit")[0]

      editDiv.addEventListener("blur", () => {
        // 这一步是保留住edit编辑框中的选区的范围对象。否则失焦后,getSelection()方法返回的选区对象已经不再是编辑框了,已经获取不到编辑框中的范围对象了。
        range = window.getSelection().getRangeAt(0)
      })

    },
    editInput(e) {
      console.log(e);
      console.log(e.target.children);
      if(e.target.children.length>0){
        this.editSpanClass()
      }
    },
    editSpanClass(){
      let spanAll = document.querySelectorAll('.edit span')
      spanAll.forEach(item=>{
        if(item.innerHTML != this.addBtnText){
          item.className = ''
        }
      })
    },
    addStrBtnFun(){
      const span = document.createElement("span")
      span.innerText = this.addBtnText
      span.className = 'selColor'
      // 如果在页面刷新再点击编辑框之前就点击了按钮,此时range中并没有选区范围对象
      if (range === "") {
        let selection = window.getSelection()
        selection.selectAllChildren(editDiv) // selectAllChildren把指定元素的所有子元素设为选中区域,并取消之前的选中区域。不包括node节点本身。
        /*
          Selection.collapseToEnd() 方法的作用是取消当前选区,并把光标定位在原选区的最末尾处,如果此时光标所处的位置是可编辑的,且它获得了焦点,则光标会在原地闪烁。
          以上selectAllChildren方法,将div中子节点全部选中,collapseToEnd方法将选中区域取消,并且将光标定位到原区的末尾。
        */
        selection.collapseToEnd()
        range = window.getSelection().getRangeAt(0) // 无论哪一步都需要保存当前编辑框的选区对象
      }
      let sel = window.getSelection()
      range.insertNode(span) // insertNode方法,在range选区开头插入一个节点
      /*
      removeAllRanges方法:删除之前的所有选区。
      这一步的意义:因为当我们点击其他区域时,选区对象已经改变,不再是编辑框中的选区对象,这时候我们接下来的操作都不会符合我们想象中的样子
     */
      sel.removeAllRanges()
      sel.addRange(range) // 这一步就是添加当前区域对象到选区对象中,所以选区对象会再次指向编辑框中的选区,不会出现别的错误操作。
      sel.collapseToEnd()
    },
    // 获取最新的内容
    getNowContent(){
      let editDiv = document.querySelectorAll('.edit')
      this.innerHtml = editDiv[0].innerHTML
      this.innerText = editDiv[0].innerText
    }

  }
}
</script>
<style scoped>
  .edit{
    width: 400px;
    height:200px;
    border:1px solid #ccc;
  }
  .edit:focus {
    outline: 0;
    border-color: #409EFF;
  }
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/711425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自定义MVC框架优化

目录 一、前言 二、优化问题 1.子控制器的初始化配置问题 2.页面跳转优化代码冗余问题 3.优化参数封装问题 三、进行优化 1.解决子控制器初始化配置 2.解决页面跳转的代码冗余问题 3.解决优化参数封装问题 4.中央控制器 一、前言 在自定义MVC框架原理中讲述了什么是…

Redis - Redis GEO实现经纬度测算距离,附近搜索范围

Redis GEO 主要用于存储地理位置信息&#xff0c;并对存储的信息进行操作&#xff0c;该功能在 Redis 3.2 版本新增 一、Redis GEO 操作方法 geoadd&#xff1a;添加地理位置的坐标 geopos&#xff1a;获取地理位置的坐标 geodist&#xff1a;计算两个位置之间的距离 geor…

client-go初级篇,从操作kubernetes到编写单元测试

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 本篇概览 尽管长篇系列《client-go实战》的内容足够丰富&#xff0c;然而内容太多每个知识点也有一定深度&#xff0c;对于打算快速学习并开始kubernetes开发…

分层架构简介

MVC是架构模式&#xff08;设计模式中的结构性模式&#xff09;&#xff0c;不是系统架构&#xff0c;更不是我们常说的三层架构 MVC的缺陷如下&#xff1a; 1.导致控制器冗余&#xff08;有大量的业务逻辑&#xff0c;可能开始没有&#xff0c;但是后来越来越多&#xff09;…

QT学习笔记5--槽函数重载解决办法

connect函数 connect(sender, signal, receiver, slot); 槽函数示例 void student:: treat(QString foodname) void student:: treat(int index) 由上可见&#xff0c;有两个名字相同&#xff0c;但形参不同的槽函数。 可以通过函数指针的方式 &#xff0c;用指针指向具体…

linux环境安装mysql8.0.32

linux环境安装mysql8.0.32 一、下载安装包二、安装前准备2.1 卸载旧版本mysql2.2 检查是否安装了 mariadb 数据库2.3 安装依赖包创建 mysql 用户 三、安装3.1 上传并解压安装包&#xff08;上传路径没有要求&#xff0c;一般在/usr/local&#xff09;3.2 初始化数据库3.3 注册数…

Java面试题6月

redis有哪些缓存淘汰策略 https://blog.51cto.com/u_11720620/5198874 生产环境内存溢出&#xff08;OOM&#xff09;问题处理方案 https://note.youdao.com/ynoteshare/index.html?id5cc182642eb02bc64197788c7722baae&typenote&_time1688287588653 jstack找出占用…

C++之GNU C的__attribute__((constructor))和((destructor))静态构造函数实现(一百四十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Elasticsearch实战(二十三)---ES数据建模与Mysql对比 一对多模型

Elasticsearch实战—ES数据建模与Mysql对比实现 一对多模型 文章目录 Elasticsearch实战---ES数据建模与Mysql对比实现 一对多模型1.一对多 模型1.1 Mysql建模 2.一对多 Index ES 数据模型2.1 类似Mysql, 依旧创建两个Index索引库2.2 采用ES架构 嵌套数组模型2.3采用ES架构 冗余…

【JUC-2】Synchronized关键字相关知识

Synchronized synchronized是Java中的关键字&#xff0c;是一种同步锁。它修饰的对象有以下几种&#xff1a; 修饰一个代码块&#xff0c;被修饰的代码块称为同步语句块&#xff0c;其作用的范围是大括号{}括起来的代码&#xff0c;作用的对象是调用这个代码块的对象&#xf…

【C++2】进程 信号 dbus

文章目录 1.进程&#xff1a;fork()&#xff0c;ps -ef (同-aux) | more2.信号&#xff1a;signal&#xff08;, EXIT&#xff09;&#xff0c;jps2.1 捕捉信号&#xff1a;ctrlc&#xff1a;22.2 捕捉信号&#xff1a;kill -9&#xff1a;92.3 捕捉信号&#xff1a;kill&#…

欧几里得算法

0x00 前言 改补的内容是一点都不会少。本章来看欧几里得算法 0x01 概述 欧几里得算法又称为辗转相除法&#xff0c;指用于计算两个非负整数a和b的最大公约数。 两个整数的最大公约数是能够同时整除他们的最大的正整数。 基本原理&#xff1a;两个整数的最大公约数等于其中…

【动态规划】子数组系列(上)

子数组问题 文章目录 【动态规划】子数组系列&#xff08;上&#xff09;1. 最大子数组和1.1 题目解析1.2 算法原理1.2.1 状态表示1.2.2 状态转移方程1.2.3 初始化1.2.4 填表顺序1.2.5 返回值 1.3 代码实现 2. 环形子数组的最大和2.1 题目解析2.2 算法原理2.2.1 状态表示2.2.2 …

C++2(表达式和关系运算)

目录 1.表达式基础 1.表达式基础 运算符重载&#xff0c;就是自己定义 - * / 之类的运算符怎么运算 C中的左值和右值 C语言左值在左侧&#xff0c;右值在右侧 在cpp中要复杂的多 能取到地址的表达式是左值 不能取到地址的表达式是右值 常量对象为代表的左值不能作为赋值语句的左…

【Linux】网络相关概念概述以及原理简单分析介绍

文章目录 [toc] Linux 网络概述网络发展独立模式网络互联局域网LAN 和 广域网WAN 认识 "协议"协议的分层网络协议栈OSI七层模型TCP/IP五层(四层)模型TCP/IP网络协议栈 与 操作系统 的关系 **重新以计算机的视角看待 网络协议栈 局域网内部通信原理简单介绍不同局域网…

mybatis web使用02

处理 transfer 请求的 servlet package com.wsd.web;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRe…

GAMES101 笔记 Lecture08 Shading 2(Shading, Pipeline and Texture Mapping)

目录 Specular Term(高光项)Ambient Term(环境光照项)Blinn-Phong Reflection ModelShading Frequencies(着色频率)Shade each triangle(flat shading)在每个三角形上进行着色Shade each vertex (Gouraud shading)(顶点着色)Shade each pixel (Phong shading)Defining Per-Vert…

【C++详解】——哈希

目录 unordered系列关联式容器 unordered_map unordered_map的接口说明 1.unordered_map的构造 2.unordered_map的容量 3.迭代器相关 4.unordered_map的元素访问 5. unordered_map的查询 6.unordered_map的修改操作 unordered_set 性能测试 底层结构——Hash 哈希…

copula简介

二元正态copula最为重要

MySQL - 自连接查询

1. 测试数据 创建 category 表 : CREATE TABLE category(categoryid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主题id,pid INT(10) NOT NULL COMMENT 父id,categoryName VARCHAR(50) NOT NULL COMMENT 主题名字,PRIMARY KEY(categoryid) ) ENGINEINNODB AUTO_INCREM…