B站UP主的"点击即改"黑科技:OOP实现就地编辑,让个性签名"awsl"!✨

87 阅读8分钟

B站UP主的"点击即改"黑科技:OOP实现就地编辑,让个性签名"awsl"!✨

在B站浏览个人主页时,你有没有发现个性签名可以直接点击编辑,无需跳转页面或填写复杂表单?这种"所见即所得"的交互方式,正是就地编辑(EditInPlace) 的魅力所在!它让用户体验变得流畅自然,避免了页面刷新带来的体验断层。今天,我们将深入剖析EditInPlace类的实现细节,从属性设计到方法实现,全方位解读其精妙之处。✨

QQ20251202-234904.png

接下来我们来实现一个 “就地编辑”(Edit-in-Place) 功能组件,目标是让用户能够在网页上直接点击一段文本进行编辑,而无需跳转页面或弹出复杂表单。

核心功能如下:

  1. 初始显示为静态文本
    页面加载时,显示如 "有了肯德基,生活好滋味" 这样的文本(用 <span> 元素呈现)。

  2. 点击文本进入编辑模式
    用户点击该文本后,文本自动替换为一个 <input> 输入框,并显示“保存”和“取消”按钮。

  3. 支持保存或取消编辑

    • 点击 “保存” :将输入框中的新内容更新为新的静态文本,并隐藏输入框和按钮。
    • 点击 “取消” :放弃修改,恢复为原来的文本显示状态。
  4. 封装为可复用的 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 || '这个家伙很懒,什么都没有留下' 是一个典型的默认值处理方式,当valuenullundefined时,会使用默认字符串。

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();
}

保存逻辑详解

  1. var value = this.fieldElement.value:获取输入框的值
  2. this.value = value:更新内部状态,确保组件内部状态与用户输入一致
  3. this.staticElement.innerHTML = value:更新DOM显示,确保UI与状态一致
  4. this.convertToText():切换回文本状态,完成编辑流程

取消逻辑

  • this.convertToText():仅需切换回文本状态,无需更新状态

设计思想:在save方法中,同时更新了this.valuestaticElement,确保组件内部状态和DOM状态一致,这是OOP思想的重要体现。

代码实现效果:

QQ20251202-235704.png

QQ20251202-235944.png

QQ20251203-000050.png

💻OOP封装的深层价值

1. 信息隐藏与接口设计

OOP的核心思想之一是信息隐藏。通过封装,我们只暴露必要的接口(如new EditInPlace()),隐藏了内部实现细节。使用者不需要知道containerElementfieldElement是如何创建的,只需关注如何使用:

const ep = new EditInPlace('slogan', '有了肯德基,生活好滋味', document.getElementById('app'));

2. 模块化与复用性

将所有逻辑封装在EditInPlace类中,实现了一个单一职责的组件。这意味着:

  • 可以在任何地方复用,只需引入这个类
  • 修改组件逻辑时,只需修改类内部,无需改动调用处
  • 代码结构清晰,逻辑分明

3. 提升可维护性

当需要修改就地编辑的交互逻辑时,只需修改类内部,而无需修改所有调用该类的代码。例如,如果需要修改按钮的样式,只需在createElement方法中修改按钮的样式,而不需要改动调用处的代码。

💻总结:OOP思想在前端开发中的价值

就地编辑(EditInPlace)是前端用户体验的典范,而OOP封装则是实现这种功能的优雅方式。通过将DOM操作、状态切换、事件处理等逻辑封装在EditInPlace类中,我们实现了:

信息隐藏:只暴露必要的接口
代码复用:一个类在多个地方使用
易于维护:修改一处,影响全局
提升体验:用户无需跳转,操作流畅自然

记住:好的OOP不是把所有东西都塞进一个类,而是把相关逻辑合理地组织在一起,让代码既易用又易维护。B站个性签名的编辑体验,正是这种思想的完美体现。下次当你在B站看到个性签名可以直接编辑时,不妨想想:这背后正是OOP思想的优雅体现!💡