当前位置: 首页 > news >正文

前端HTML contenteditable 属性使用指南 - 教程

​​什么是 contenteditable?

<p contenteditable="true">可编辑的段落</p>

属性值说明
contenteditable 的三种值:
true:元素可编辑
false:元素不可编辑
inherit:继承父元素的可编辑状态

<p contenteditable="false">不可编辑的段落</p>
<div contenteditable="true">点击编辑此内容</div>
<p contenteditable="inherit">继承父元素的可编辑状态</p>

核心功能实现​

保存编辑内容​
<divstyle="margin-left: 36px;"v-html="newData"contenteditable="true"ref="ediPending2Div"class="editable"@blur
="updateContent"@input
="handleInput"@focus
="saveCursorPosition"@keydown.enter.prevent
="handleEnterKey"></div>
// 更新内容
updateContent(
) {
this.isEditing = false
if (
this.rawData !==
this.editContent) {
this.submitChanges(
)
this.editContent =
this.rawData
}
}
,
编辑时光标位置的设置
<divstyle="margin-left: 36px;"v-html="newData"contenteditable="true"ref="ediPending2Div"class="editable"@blur
="updateContent"@input
="handleInput"@focus
="saveCursorPosition"@keydown.enter.prevent
="handleEnterKey"></div>
// 保存光标位置
saveCursorPosition(
) {
const selection = window.getSelection(
)
if (selection.rangeCount >
0
) {
const range = selection.getRangeAt(0
)
this.lastCursorPos = {
startContainer: range.startContainer,
startOffset: range.startOffset,
endOffset: range.endOffset
}
}
}
,
// 恢复光标位置
restoreCursorPosition(
) {
if (!
this.lastCursorPos || !
this.isEditing)
return
const selection = window.getSelection(
)
const range = document.createRange(
)
try {
range.setStart(
this.lastCursorPos.startContainer,
Math.min(
this.lastCursorPos.startOffset,
this.lastCursorPos.startContainer.length)
)
range.setEnd(
this.lastCursorPos.startContainer,
Math.min(
this.lastCursorPos.endOffset,
this.lastCursorPos.startContainer.length)
)
selection.removeAllRanges(
)
selection.addRange(range)
}
catch (e) {
// 出错时定位到末尾
range.selectNodeContents(
this.$refs.ediPending2Div)
range.collapse(false
)
selection.removeAllRanges(
)
selection.addRange(range)
}
}
,
// 处理输入
handleInput(
) {
this.saveCursorPosition(
)
this.rawData =
this.$refs.ediPending2Div.innerHTML
}
,
处理换行失败的问题(需要回车两次触发)
// 给数组添加回车事件
handleEnterKey(e
) {
// 阻止默认回车行为(创建新div)
e.preventDefault(
)
;
// 获取当前选区
const selection = window.getSelection(
)
;
if (!selection.rangeCount)
return
;
const range = selection.getRangeAt(0
)
;
const br = document.createElement('br'
)
;
// 插入换行
range.deleteContents(
)
;
range.insertNode(br)
;
// 移动光标到新行
range.setStartAfter(br)
;
range.collapse(true
)
;
selection.removeAllRanges(
)
;
selection.addRange(range)
;
// 触发输入更新
this.handleInput(
)
;
}
,

踩坑案例

完整代码展示

带数组代码

<template><div style="margin-left: 36px;" v-loading="loading_"contenteditable="true"ref="editPendingDiv"class='editable'@blur="updateContent"@input="handleInput"@focus="saveCursorPosition"@keydown.enter.prevent="handleEnterKey"><p class="pending_title">会议待办</p><p>提炼待办事项如下:</p><div v-for="(item, index) in newData" :key="index"class="todo-item"><div class="text_container"><!-- <img src="@/assets/404.png" alt="icon"class="icon-img">--><p><span class="icon-span">AI</span>{{item}}</p></div></div></div></template><script>// 会议待办事项组件import {todoList}from '@/api/audio';import router from '@/router';exportdefault {name: 'pendingResult',props: {// items: {// type: Array,// required: true// }},data() {return {rawData:null,editContent: '', // 编辑内容缓存lastCursorPos:null, // 光标位置记录isEditing: false,loading_:false,dataList: [],routerId:this.$route.params.id};},computed: {newData () {// 在合格换行后下面添加margin-botton: 10pxreturnthis.dataList}},watch: {newData() {this.$nextTick(this.restoreCursorPosition)this.$nextTick(this.sendHemlToParent)}},mounted() {this.$refs.editPendingDiv.addEventListener('focus', () =>{this.isEditing = true})},created() {this.getDataList();},methods: {// 给数组添加回车事件handleEnterKey(e) {// 阻止默认回车行为(创建新div)e.preventDefault();// 获取当前选区const selection = window.getSelection();if (!selection.rangeCount)return;const range = selection.getRangeAt(0);const br = document.createElement('br');// 插入换行range.deleteContents();range.insertNode(br);// 移动光标到新行range.setStartAfter(br);range.collapse(true);selection.removeAllRanges();selection.addRange(range);// 触发输入更新this.handleInput();},// 发送生成数据sendHemlToParent(){this.$nextTick(()=>{const htmlString =this.$refs.editPendingDiv.innerHTMLconsole.log('获取修改',htmlString)this.$emit('editList',htmlString)})},// 保存光标位置saveCursorPosition() {const selection = window.getSelection()if (selection.rangeCount >0) {const range = selection.getRangeAt(0)this.lastCursorPos = {startContainer: range.startContainer,startOffset: range.startOffset,endOffset: range.endOffset}}},// 恢复光标位置restoreCursorPosition() {if (!this.lastCursorPos || !this.isEditing)returnconst selection = window.getSelection()const range = document.createRange()try {range.setStart(this.lastCursorPos.startContainer,Math.min(this.lastCursorPos.startOffset,this.lastCursorPos.startContainer.length))range.setEnd(this.lastCursorPos.startContainer,Math.min(this.lastCursorPos.endOffset,this.lastCursorPos.startContainer.length))selection.removeAllRanges()selection.addRange(range)}catch (e) {// 出错时定位到末尾range.selectNodeContents(this.$refs.editPendingDiv)range.collapse(false)selection.removeAllRanges()selection.addRange(range)}},// 处理输入handleInput() {this.saveCursorPosition()this.rawData =this.$refs.editPendingDiv.innerHTML},// 更新内容// updateContent() {// this.isEditing = false// if (this.rawData !== this.editContent) {// this.submitChanges()// this.editContent = this.rawData// }// },updateContent() {this.isEditing = false;// 清理HTML格式const cleanedHTML =this.rawData.replace(/<div><br><\/div>/g, '<br>').replace(/<p><br><\/p>/g, '<br>');if (cleanedHTML !==this.editContent) {this.submitChanges(cleanedHTML);}},// 提交修改submitChanges() {// 这里添加API调用逻辑console.log('提交内容:',this.rawData)this.$emit('editList',this.rawData)},async getDataList() {const id = {translate_task_id:this.routerId};this.loading_=truetry {const res=await todoList(id)if (res.code === 0) {if (res.data.todo_text == [] || res.data.todo_text ===null) {this.$message.warning("暂无待办事项");return;}// console.log("会议纪要数据:", res.data);this.dataList=res.data.todo_text}}finally {this.loading_=false}// const normalizedText = res.data.todo_text.replace(/\/n/g, '\n');// // 分割文本并过滤空行// this.dataList = normalizedText.split('\n')// .filter(line => line.trim().length > 0)// .map(line => line.trim());}}}</script><style scoped>.pending_title {/* font-size: 20px; *//* font-family: "宋体"; *//* font-weight: bold; */margin-bottom: 20px;}.text_container {display: flex;align-items: center;}.icon-img {width: 20px;height: 20px;margin-right: 10px;}.editable {/* 确保可编辑区域行为正常 */user-select: text;white-space: pre-wrap;outline: none;}.todo-item {display: flex;align-items: center;margin: 4px 0;}/* 防止图片被选中 */.icon-span {pointer-events: none;user-select: none;margin-right: 6px;font-weight: 700;color: #409EFF;}</style>

不带数组代码

<template><div><divstyle="margin-left: 36px;"v-html="newData"contenteditable="true"ref="ediPending2Div"class="editable"@blur="updateContent"@input="handleInput"@focus="saveCursorPosition"@keydown.enter.prevent="handleEnterKey"></div></div></template><script>// 会议待办事项组件222exportdefault {name: 'pendingResult2',props: {dataList: {type: Object,required: true}},data() {return {rawData:null,editContent: '', // 编辑内容缓存lastCursorPos:null, // 光标位置记录isEditing: false,};},computed: {newData () {returnthis.dataList.todo_text}},watch: {newData() {this.$nextTick(this.restoreCursorPosition)}},mounted() {this.$refs.ediPending2Div.addEventListener('focus', () =>{this.isEditing = true})},created() {// console.log(":", this.dataList);},methods: {// 给数组添加回车事件handleEnterKey(e) {// 阻止默认回车行为(创建新div)e.preventDefault();// 获取当前选区const selection = window.getSelection();if (!selection.rangeCount)return;const range = selection.getRangeAt(0);const br = document.createElement('br');// 插入换行range.deleteContents();range.insertNode(br);// 移动光标到新行range.setStartAfter(br);range.collapse(true);selection.removeAllRanges();selection.addRange(range);// 触发输入更新this.handleInput();},// 保存光标位置saveCursorPosition() {const selection = window.getSelection()if (selection.rangeCount >0) {const range = selection.getRangeAt(0)this.lastCursorPos = {startContainer: range.startContainer,startOffset: range.startOffset,endOffset: range.endOffset}}},// 恢复光标位置restoreCursorPosition() {if (!this.lastCursorPos || !this.isEditing)returnconst selection = window.getSelection()const range = document.createRange()try {range.setStart(this.lastCursorPos.startContainer,Math.min(this.lastCursorPos.startOffset,this.lastCursorPos.startContainer.length))range.setEnd(this.lastCursorPos.startContainer,Math.min(this.lastCursorPos.endOffset,this.lastCursorPos.startContainer.length))selection.removeAllRanges()selection.addRange(range)}catch (e) {// 出错时定位到末尾range.selectNodeContents(this.$refs.ediPending2Div)range.collapse(false)selection.removeAllRanges()selection.addRange(range)}},// 处理输入handleInput() {this.saveCursorPosition()this.rawData =this.$refs.ediPending2Div.innerHTML},// 更新内容updateContent() {this.isEditing = falseif (this.rawData !==this.editContent) {this.submitChanges()this.editContent =this.rawData}},// 提交修改submitChanges() {// 这里添加API调用逻辑console.log('提交内容:',this.rawData)this.$emit('editList',this.rawData)},getDataList() {},},}</script><style scoped>::v-deep .el-loading-mask{display: none !important;}p {/* margin: 0.5em 0; *//* font-family: "思源黑体 CN Regular"; *//* font-size: 18px; */}img {width: 20px;height: 20px;margin-right: 10px;}.indent_paragraph {text-indent: 2em;/* 默认缩进 */}.pending_title {/* font-size: 20px; *//* font-family: "宋体"; *//* font-weight: bold; */margin-bottom: 20px;}.text_container {display: flex;align-items: center;}.icon-img {width: 20px;height: 20px;margin-right: 10px;}.editable {/* 确保可编辑区域行为正常 */user-select: text;white-space: pre-wrap;outline: none;}.todo-item {display: flex;align-items: center;margin: 4px 0;}/* 防止图片被选中 */.icon-span {pointer-events: none;user-select: none;margin-right: 6px;font-weight: 700;color: #409EFF;}</style>
效果展示

在这里插入图片描述

http://www.hskmm.com/?act=detail&tid=26178

相关文章:

  • luogu P1648 看守
  • 题解:P11219 【MX-S4-T3】「yyOI R2」youyou 的序列 II
  • Seismic Unix 基础使用
  • 2025实验室净化厂家/实验室装修厂家/实验室建设厂家权威推荐榜:专业设计与洁净技术实力之选
  • 修改注册表,实现电脑小键盘开机自启(NumLock灯常亮)
  • 完整教程:nav2笔记-250603
  • Bartender打印乱序条码教程
  • 多Agent协作入门:基于A2A协议的Agent通信
  • 时尚产品需求预测与库存优化模型解析
  • 自制带得分和推荐走法的象棋视频
  • DP分析黑科技——闫氏DP分析法
  • MUGEN游戏引擎等一系列相关杂谈
  • # 20232313 2025-2026-1 《网络与系统攻防技术》实验一实验报告 - 20232313
  • 一生一芯学习:PA2:输入输出
  • vector使用中的一个小问题
  • OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering() - 指南
  • 2025.10.7——2绿
  • 完整教程:无人机避障——感知部分(Ubuntu 20.04 复现Vins Fusion跑数据集)胎教级教程
  • 我真的博了
  • 2025.10.6——1绿1蓝
  • 深入解析:人工智能-Chain of Thought Prompting(思维链提示,简称CoT)
  • 年龄排序
  • 二分图最大匹配 输出具体方案
  • 我的联想小新潮7000笔记本的优化
  • Go语言之接口与多态 -《Go语言实战指南》 - 指南
  • 地球科学概论
  • 2025多校冲刺CSP模拟赛4 总结
  • 多路归并、败者树、置换-选择排序、最佳归并树
  • 看vue文档记录(未整理)
  • Spring5笔记