点击即可变身的编辑器:原生 JS 也能写出丝滑交互

57 阅读3分钟

一句话爽文引言
你有没有遇过这种时刻:
改个个人简介 → 页面跳转三次 → 心态崩两次。
当用户体验遭遇“跳转地狱”,你就知道——是时候让 就地编辑(Edit In Place) 出场了。

它就像 UI 世界的“变形金刚”:
静态文字 ➜ 一点即变输入框 ➜ 保存又变回文字。
丝滑得像魔术。

今天,我们不但要把它造出来,还要把它从面条代码封成一个“可无限复用的组件工厂”。

准备好开启奇幻之旅了吗?🧙‍♂️✨


🏕️ 一、认识 EditInPlace:一个会 72 变的小部件

1.1 它是什么?

一句话,它就是:

点一下 → 文字变输入框 → 改完自动变回去

知乎昵称、微博签名、Notion 标题等,都在用这种小组件。

下面是我们要优化与封装的原始(但非常有价值的)核心代码:

function EditInPlace(id, value, parentElement) {
  this.id = id;
  this.value = value || '这个家伙很懒,什么都没有留下';
  this.parentElement = parentElement;
  this.containerElement = null;
  this.saveButton = null;
  this.cancelButton = null;
  this.fieldElement = null;
  this.staticElement = null;

  this.createElement();
  this.attachEvent();
}

1.2 最形象的比喻:建筑公司的标准化施工

一个 EditInPlace 实例,就像盖一栋“小别墅”:

  • id=门牌号
  • value=初始装修方案
  • parentElement=地块位置
  • createElement() =盖房子
  • attachEvent() =安装智能家居

写这段代码就像开启一个“智能别墅制造流水线”。


🏗️ 二、createElement:从 0 到 1 造一栋小别墅

此方法负责 DOM 的 全部结构搭建

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.cancelButton = document.createElement('input');
  this.cancelButton.value = '取消';
  this.containerElement.appendChild(this.cancelButton);

  this.parentElement.appendChild(this.containerElement);

  this.convertToText();
}

一个组件 = 一个小房子结构:

🧱 构建总览

部件作用比喻
div容器✔ 房子的承重墙
span只读文字✔ 客厅展示区
input可编辑输入框✔ 临时施工区
保存 / 取消按钮操作区✔ 控制面板

最妙的一行:

this.convertToText();

相当于交房时说一句:“先以展示模式交付。”


🎬 三、两种模式:展示(Text) vs 编辑(Field)

3.1 convertToText:大幕拉开

convertToText: function() {
  this.fieldElement.style.display = 'none';
  this.saveButton.style.display = 'none';
  this.cancelButton.style.display = 'none';
  this.staticElement.style.display = 'inline';
}

展示模式:
只看演员(文字)
后台人员统统藏起来。

3.2 convertToField:后台上线

convertToField: function() {
  this.staticElement.style.display = 'none';
  this.fieldElement.value = this.value;
  this.fieldElement.style.display = 'inline';
  this.saveButton.style.display = 'inline';
  this.cancelButton.style.display = 'inline';
}

编辑模式:
演员下场,工作人员上台(输入框 + 按钮)

最关键的一句是:

this.fieldElement.value = this.value;

保证每次进入编辑状态看到的都是最新数据。

这是 UI 稳定性的重要保障。


🧲 四、attachEvent:给别墅装“智能家居”

attachEvent: function() {
  this.staticElement.addEventListener('click', () => {
    this.convertToField();
  });
  this.saveButton.addEventListener('click', () => {
    this.save();
  });
  this.cancelButton.addEventListener('click', () => {
    this.cancel();
  });
}

🎯 为什么用箭头函数?

因为普通函数 inside addEventListener,会让 this 指向 DOM 节点,而不是组件实例。

箭头函数就像绑定了“户口本”:

  • 写在哪,就永远指向谁。

🧩 五、save 与 cancel:两兄弟的性格

save: function() {
  var value = this.fieldElement.value;
  this.value = value;
  this.staticElement.innerHTML = value;
  this.convertToText();
}

cancel: function() {
  this.convertToText();
}

保存:
✔ 读取输入值
✔ 更新数据源
✔ 更新 UI
✔ 切回展示模式

取消:
✔ 一键恢复原样


🧬 六、OOP 的价值:你写的不是代码,是“组件生态”

6.1 可复用

你可以轻松创建 10 个、100 个:

new EditInPlace('name', '张三', app);
new EditInPlace('bio', '简介内容', profile);

6.2 封装

使用者根本不需要知道:

  • span 是怎么变 input 的
  • 事件是如何绑定的
  • DOM 是怎么操作的

只需要一句:

new EditInPlace(...)

6.3 模块化

把类丢进一个文件,直接插上即用:

<script src="./edit_in_place.js"></script>

像插 U 盘一样的复用性。


🎨 七、体验层面的“隐藏 buff”

7.1 自动聚焦

this.fieldElement.focus();

7.2 键盘支持

this.fieldElement.addEventListener('keydown', e => {
  if (e.key === 'Enter') this.save();
  if (e.key === 'Escape') this.cancel();
});

7.3 性能最优策略

  • DOM 创建一次
  • 后续只是切换 display

🚀 八、未来升级路线:从“毛坯房”到“智能豪宅”

8.1 后端持久化

加入 fetch:

await fetch(`/api/update/${this.id}`, {
  method: 'POST',
  body: JSON.stringify({ value })
});

8.2 主题皮肤

支持 options:

new EditInPlace(id, value, parent, {
  cssClass: 'dark-theme'
});

🧙‍♂️ 九、总结:OOP 的“道”与“术”

🛠️ 術:写法层面的套路

  • 职责分离
  • 模块拆分
  • 单一职责
  • 清晰的 API

🧠 道:思维层面的觉醒

你不再是写代码,而是在“造物”:

  • 让代码能复用
  • 让代码能协作
  • 让代码能成长

一个类 = 一个微型生态系统
而 EditInPlace 就是你迈向 OOP 世界的第一只“宝可梦”。