B站UP主的"点击即改"黑科技:OOP实现就地编辑,让个性签名"awsl"!✨
在B站浏览个人主页时,你有没有发现个性签名可以直接点击编辑,无需跳转页面或填写复杂表单?这种"所见即所得"的交互方式,正是就地编辑(EditInPlace) 的魅力所在!它让用户体验变得流畅自然,避免了页面刷新带来的体验断层。今天,我们将深入剖析EditInPlace类的实现细节,从属性设计到方法实现,全方位解读其精妙之处。✨
接下来我们来实现一个 “就地编辑”(Edit-in-Place) 功能组件,目标是让用户能够在网页上直接点击一段文本进行编辑,而无需跳转页面或弹出复杂表单。
核心功能如下:
-
初始显示为静态文本
页面加载时,显示如"有了肯德基,生活好滋味"这样的文本(用<span>元素呈现)。 -
点击文本进入编辑模式
用户点击该文本后,文本自动替换为一个<input>输入框,并显示“保存”和“取消”按钮。 -
支持保存或取消编辑
- 点击 “保存” :将输入框中的新内容更新为新的静态文本,并隐藏输入框和按钮。
- 点击 “取消” :放弃修改,恢复为原来的文本显示状态。
-
封装为可复用的 OOP 类
通过EditInplace构造函数,传入 ID、初始值和挂载容器,即可在任意位置快速创建一个就地编辑区域,实现逻辑与 UI 的封装和复用。
💻代码展示:
// html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./edit_in_place.js"></script>
<script>
// EditInplace 类
// OOP
const ep = new EditInplace(
'slogan',
'有了肯德基,生活好滋味',
document.getElementById('app')
);
console.log(ep);
</script>
</body>
</html>
// js
/**
* @func EditInPlace 就地编辑
@params {string} value 初始值
@params {element} parentElement 挂载点
@params {string} id 自身ID
*/
function EditInplace(id, value, parentElement) {
// {} 空对象 this指向它
this.id = id;
this.value = value || '这个家伙很懒,什么都没有留下';
this.parentElement = parentElement;
this.containerElement = null; // 空对象
this.saveButton = null; // 保存
this.canaceButton = null; // 取消
this.fieldElement = null; // input
this.staticElement = null; // span
// 代码比较多,按功能分模块 拆函数
this.createElement(); // DOM 对象创建
this.attachEvent(); // 事件添加
}
EditInplace.prototype = {
// 封装了DOM 操作
createElement: function () {
// DOM 操作 内存里面
this.containerElement = document.createElement('div');
// console.log(this.containerElement,
// // this绑定
// Object.prototype.toString.apply(this.containerElement)
// );
this.containerElement.id = this.id;
// 值
this.staticElement = document.createElement('span');
this.staticElement.innerHTML = this.value;
this.containerElement.appendChild(this.staticElement);
// 输入框
this.fieldElement = document.createElement('input');
this.fieldElement.type = 'text';
this.fieldElement.value = this.value;
this.containerElement.appendChild(this.fieldElement);
this.parentElement.appendChild(this.containerElement);
this.saveButton = document.createElement('input');
this.saveButton.type = 'button';
this.saveButton.value = '保存',
this.containerElement.appendChild(this.saveButton);
this.canaceButton = document.createElement('input');
this.canaceButton.type = 'button';
this.canaceButton.value = '取消',
this.containerElement.appendChild(this.canaceButton);
this.convertToText(); // 切换到文本显示状态
// this.fieldElement();
},
convertToText: function () {
this.fieldElement.style.display = 'none', // 隐藏
this.staticElement.style.display = 'inline', // 可见
this.saveButton.style.display = 'none',
this.canaceButton.style.display = 'none'
},
convertToFiled: function () {
this.fieldElement.style.display = 'inline', // 可见
this.staticElement.style.display = 'none' // 隐藏
this.saveButton.style.display = 'inline',
this.canaceButton.style.display = 'inline'
},
attachEvent: function() {
this.staticElement.addEventListener('click',
() => {
this.convertToFiled(); // 切换到输入框显示状态
}
);
this.saveButton.addEventListener('click',
() => {
this.save();
}
);
this.canaceButton.addEventListener('click',
() => {
this.cance();
}
);
},
save: function() {
var value = this.fieldElement.value;
// fetch 后端存储
this.value = value;
this.staticElement.innerHTML = value;
this.convertToText();
},
cance: function() {
this.convertToText();
}
}
💻代码深度解析:OOP实现就地编辑的精妙之处
一、类的属性设计:构建组件的基石
/**
* @func EditInPlace 就地编辑
* @params {string} value 初始值
* @params {element} parentElement 挂载点
* @params {string} id 自身ID
*/
function EditInPlace(id, value, parentElement) {
this.id = id;
this.value = value || '这个家伙很懒,什么都没有留下';
this.parentElement = parentElement;
this.containerElement = null;
this.saveButton = null;
this.canaceButton = null;
this.fieldElement = null;
this.staticElement = null;
}
属性详解
this.id:组件的唯一标识,用于DOM操作和CSS选择器(如#slogan)。在HTML中,每个元素的ID应该是唯一的,这确保了组件可以被准确地定位和操作。
this.value:组件的初始值,提供默认值避免空值问题。value || '这个家伙很懒,什么都没有留下' 是一个典型的默认值处理方式,当value为null或undefined时,会使用默认字符串。
this.parentElement:组件的挂载点,决定组件显示在页面的哪个位置。通过传入DOM元素,我们可以将组件插入到指定位置,这使得组件非常灵活,可以适应各种布局。
this.containerElement:组件的容器元素,用于组织内部结构。它是一个div元素,作为所有其他元素的父容器,确保了DOM结构的清晰和组织性。
this.saveButton:保存按钮的DOM引用,用于事件绑定和状态控制。通过这个引用,我们可以在保存操作时获取按钮状态。
this.canaceButton:取消按钮的DOM引用,与保存按钮类似,用于事件绑定和状态控制。
this.fieldElement:输入框的DOM引用,用于获取用户输入和状态控制。这是用户编辑内容的主要输入区域。
this.staticElement:显示文本的DOM引用,用于显示当前值和状态控制。这是用户看到的初始文本内容。
设计思想:这些属性构成了组件的核心状态,所有交互逻辑都基于这些状态。OOP思想告诉我们,应该将数据和操作数据的方法封装在一起,而不是散落在全局作用域中。
二、方法实现:封装DOM操作与交互逻辑
1. DOM创建:createElement()
createElement: function() {
this.containerElement = document.createElement('div');
this.containerElement.id = this.id;
this.staticElement = document.createElement('span');
this.staticElement.innerHTML = this.value;
this.containerElement.appendChild(this.staticElement);
this.fieldElement = document.createElement('input');
this.fieldElement.type = 'text';
this.fieldElement.value = this.value;
this.containerElement.appendChild(this.fieldElement);
this.saveButton = document.createElement('input');
this.saveButton.type = 'button';
this.saveButton.value = '保存';
this.containerElement.appendChild(this.saveButton);
this.canaceButton = document.createElement('input');
this.canaceButton.type = 'button';
this.canaceButton.value = '取消';
this.containerElement.appendChild(this.canaceButton);
this.parentElement.appendChild(this.containerElement);
this.convertToText();
}
关键代码详解:
this.containerElement = document.createElement('div'):创建容器元素,作为组件的根节点。this.containerElement.id = this.id:为容器设置ID,确保组件可以被准确地定位。this.staticElement = document.createElement('span'):创建显示文本的元素,使用span标签,因为它不会产生额外的块级布局。this.staticElement.innerHTML = this.value:将初始值设置为文本内容,使用innerHTML确保可以处理HTML字符。this.containerElement.appendChild(this.staticElement):将文本元素添加到容器中。this.fieldElement = document.createElement('input'):创建输入框元素。this.fieldElement.type = 'text':设置输入框类型为文本。this.fieldElement.value = this.value:设置输入框初始值,确保编辑时显示正确内容。this.containerElement.appendChild(this.fieldElement):将输入框添加到容器中。this.saveButton = document.createElement('input'):创建保存按钮。this.saveButton.type = 'button':设置按钮类型为按钮。this.saveButton.value = '保存':设置按钮显示文本。this.containerElement.appendChild(this.saveButton):将保存按钮添加到容器中。this.canaceButton = document.createElement('input'):创建取消按钮。this.canaceButton.type = 'button':设置按钮类型。this.canaceButton.value = '取消':设置按钮显示文本。this.containerElement.appendChild(this.canaceButton):将取消按钮添加到容器中。this.parentElement.appendChild(this.containerElement):将整个组件添加到挂载点。this.convertToText():初始化为文本显示状态。
设计思想:DOM操作被封装在createElement方法中,隐藏了实现细节,使用者无需关心如何创建元素。
2. 状态切换:convertToText() 和 convertToFiled()
convertToText: function() {
this.fieldElement.style.display = 'none';
this.staticElement.style.display = 'inline';
this.saveButton.style.display = 'none';
this.canaceButton.style.display = 'none';
},
convertToFiled: function() {
this.fieldElement.style.display = 'inline';
this.staticElement.style.display = 'none';
this.saveButton.style.display = 'inline';
this.canaceButton.style.display = 'inline';
}
状态切换原理:
- 文本显示状态:
staticElement可见,fieldElement隐藏,按钮隐藏 - 编辑状态:
fieldElement可见,staticElement隐藏,按钮显示
关键代码详解:
this.fieldElement.style.display = 'none':隐藏输入框this.staticElement.style.display = 'inline':显示静态文本this.saveButton.style.display = 'none':隐藏保存按钮this.canaceButton.style.display = 'none':隐藏取消按钮
设计思想:状态切换是就地编辑的核心,通过切换状态实现"显示文本"和"显示输入框"的交互。两个方法互为逆操作,确保状态切换的完整性。
3. 事件绑定:attachEvent()
attachEvent: function() {
this.staticElement.addEventListener('click', () => {
this.convertToFiled();
});
this.saveButton.addEventListener('click', () => {
this.save();
});
this.canaceButton.addEventListener('click', () => {
this.cance();
});
}
事件绑定详解:
this.staticElement.addEventListener('click', () => { this.convertToFiled(); }):为静态文本绑定点击事件,点击后切换到编辑状态。this.saveButton.addEventListener('click', () => { this.save(); }):为保存按钮绑定点击事件,点击后触发保存逻辑。this.canaceButton.addEventListener('click', () => { this.cance(); }):为取消按钮绑定点击事件,点击后触发取消逻辑。
箭头函数:使用箭头函数确保this指向正确,避免在事件回调中丢失上下文。
设计思想:事件处理逻辑被封装在attachEvent方法中,避免了全局事件污染,提高了代码可维护性。
4. 保存与取消:save() 和 cance()
save: function() {
var value = this.fieldElement.value;
this.value = value;
this.staticElement.innerHTML = value;
this.convertToText();
},
cance: function() {
this.convertToText();
}
保存逻辑详解:
var value = this.fieldElement.value:获取输入框的值this.value = value:更新内部状态,确保组件内部状态与用户输入一致this.staticElement.innerHTML = value:更新DOM显示,确保UI与状态一致this.convertToText():切换回文本状态,完成编辑流程
取消逻辑:
this.convertToText():仅需切换回文本状态,无需更新状态
设计思想:在save方法中,同时更新了this.value和staticElement,确保组件内部状态和DOM状态一致,这是OOP思想的重要体现。
代码实现效果:
💻OOP封装的深层价值
1. 信息隐藏与接口设计
OOP的核心思想之一是信息隐藏。通过封装,我们只暴露必要的接口(如new EditInPlace()),隐藏了内部实现细节。使用者不需要知道containerElement或fieldElement是如何创建的,只需关注如何使用:
const ep = new EditInPlace('slogan', '有了肯德基,生活好滋味', document.getElementById('app'));
2. 模块化与复用性
将所有逻辑封装在EditInPlace类中,实现了一个单一职责的组件。这意味着:
- 可以在任何地方复用,只需引入这个类
- 修改组件逻辑时,只需修改类内部,无需改动调用处
- 代码结构清晰,逻辑分明
3. 提升可维护性
当需要修改就地编辑的交互逻辑时,只需修改类内部,而无需修改所有调用该类的代码。例如,如果需要修改按钮的样式,只需在createElement方法中修改按钮的样式,而不需要改动调用处的代码。
💻总结:OOP思想在前端开发中的价值
就地编辑(EditInPlace)是前端用户体验的典范,而OOP封装则是实现这种功能的优雅方式。通过将DOM操作、状态切换、事件处理等逻辑封装在EditInPlace类中,我们实现了:
✅ 信息隐藏:只暴露必要的接口
✅ 代码复用:一个类在多个地方使用
✅ 易于维护:修改一处,影响全局
✅ 提升体验:用户无需跳转,操作流畅自然
记住:好的OOP不是把所有东西都塞进一个类,而是把相关逻辑合理地组织在一起,让代码既易用又易维护。B站个性签名的编辑体验,正是这种思想的完美体现。下次当你在B站看到个性签名可以直接编辑时,不妨想想:这背后正是OOP思想的优雅体现!💡