我给花墨加了一个“想法”功能
根据花墨产品经理方导的灵感,我为花墨添加了一个类似于微信读书、知乎的段落想法功能。
本需求的灵感来自于本站产品经理
且爱看知乎小说的方导。
我其实一直很喜欢微信读书的段落想法功能。
用户可以选择指定的段落、文本,记下自己的想法,并与其他用户进行交流。
这样的想法针对性更强,在面对一篇比较长的文章时,不需要在评论区写长篇大论,而是可以就具体的细节文本进行讨论。
作为作者,也可以在一些段落上写一些不适合放在正文的解释说明。
但我确实从未想过将这个巧妙的功能添加至我的网站,可能它会下意识的让我觉得难以实现、不具备安全性、交互逻辑容易出错。
于是,在方导的这一波灵感启发下,我尝试为我的博客添加这个想法功能。
不出意外的话,你可以通过这句话看到我为花墨写下的第一个想法。
同时你也可以在本文的任意段落(除代码块)进行想法功能的测试,但不要在其他文章测试哦。
如果仅仅为了尝试功能,请添加“测试”、“功能测试”等相关词汇,让我知道你是在尝试、测试功能,“111”、“asdf”等无意义文字无法通过审核。
接下来我会简单讲一讲我是如何实现该功能的,分为两部分:设计思路与技术实现,不感兴趣的读者可以直接略过啦!
设计思路
这个功能简单来说就一段话:
读者可以在博客正文里框选一段文字,就地写下一条想法;
想法提交后,那段文字就会显示一条虚线下划线;
审核通过后的虚线会使用正常样式,待审核想法则会使用更浅的颜色;
任何人点它都能看到这一段上的所有想法,也能就地写自己的想法。
交互逻辑
用户打开想法浮窗只有两种方式:第一种,框选文字,点击“写想法”。第二种,点击带虚线的文字。
同时我参考了微信读书的设计逻辑,有想法的段落不可重叠,每个段落都是独立的。
这里的段落,指的是一个文本选区/文本范围,而非真的一段话,或一个
<p>段落
看似很简单,但我考虑到框选文字存在几个交互、安全上的问题,这也是该功能最复杂的部分:
- 如果用户框选了已带有想法的段落怎么办?
- 如果两个用户同时框选一段相同或部分相同的段落,导致提交冲突怎么办?
- 如果用户恶意框选了整篇文章怎么办?
- 如果用户框选的段落后期被我修改了怎么办?
- ……
我需要尽可能的考虑各种交互所导致的后果,并提前预防和制定规则。
风险控制
针对潜在的问题,我制定了一些防止出问题的设定,例如:
- 所有想法都需要经过我的人工审核,我可以判断该想法是否为恶意评论。
- 框选文本限制,桌面端限制字数,手机端限制段数,并采用后端兜底。
- 设定发布间隔、设定待审核想法上限数。
- 不可点赞、不可回复,明确“轻评论”定位。
- 采用锚点定位来判断段落的具体位置,通过前后端协作避免交互冲突。
- ……
相关规则实在是太多,于是这里便简单介绍几条。
功能细节
为保证用户的使用体验,我在许多细节处理上力争人性化:
- 浮窗尽可能不遮挡文字,用选区自己的矩形算出该往哪个方向铺开,并且可以拖拽。
- 浮窗通过本地缓存存放已经编写的文字草稿,防止误关丢失。
- 虚线点击的行为意味着“查看想法”,所以不会默认显示“写想法”的表单,只有框选后才会默认显示“写想法”表单。
- 不喜欢该功能的用户可以在顶部“关闭想法”,下一次访问也会保持生效。
- 设置“什么是想法?”入口,给初次见到该功能的用户一个了解的渠道。
- 浮窗和框选文字高亮并不会随着滚动关闭,因为我考虑到读者可能会在写一半的时候去浏览之前忘记的内容。
- 想法提交后会立刻展示虚线,待审核想法的段落会设置为更浅色的虚线。
- 表单样式和逻辑基本参考带有安全检查的评论区表单,依旧可以使用表情包,且支持Markdown
如果你在使用的时候有任何优化建议和想法,欢迎告诉我。
技术实现
其实该功能在技术上的唯一难点,就是如何正确获取用户框选的文字、正确渲染有想法的文字。
因为正文是 Markdown 渲染出来的 HTML,每次数据变化都会整块重建 DOM。所以不能存「第几个元素、第几个字符」这种路径,一次渲染就全废了。
我用的办法是把所有正文文字拼成一条长长的字符轴:
- 找出正文里所有可批注的块(段落、列表项、标题、表格单元格、引用…),把它们的纯文本按顺序拼起来,块与块之间插入一个换行符;
- 代码块整块排除(它里面有复制按钮之类的节点,混进来会打架);
- 用户框选的范围,就换算成这条字符轴上的起止位置(两个数字);
- 想法存的就是这两个数字,外加选中的原文和它前后的各 32 个字。
这样带来的最大好处是:“两个想法对应的文本范围有没有重叠”退化成了两个数字区间是否相交,一行判断就够,不用任何几何计算。
渲染时反过来:按数字找到对应的文字节点,把它包成一个 <mark> 元素来显示虚线。
虚线渲染
这里主要讲一下虚线渲染的逻辑。
我最开始的想法是做一个标记表,通过标记表来获取文章中的哪一段有虚线,但后来我发现这样的逻辑行不通。
一旦文章有一定修改(虽然几率很低),所有的标记都会乱掉。
所以我最后使用的逻辑是“先清干净再画”。
因为渲染虚线会把文字节点切开,每次重画之前,先把上一条虚线拆掉、把切碎的文字节点重新合并回去,再从头去画。这样不管文章切换、重新渲染多少次,结果都一样,不会越画越碎。
虽然可能会带来额外的性能负载,但换来的稳定性和可控性是值得的。
段落修改抢救
针对原文修改,导致想法对应的段落消失导致想法消失的问题,我也采取了一定的解决方案。
我将所有的想法以段落作为基础,每个段落下才是想法(而不是将一个一个想法作为基础)
想法是单独存放在一张表中的,并不是绑定在博客中。
当段落文本修改的时候,我先通过段落定位到原来的文本区域,再利用想法保存的原文以及前后文锚点,在修改后的文本中重新寻找位置。
不过我其实很少修改博客内容,但避免真的出现这种情况丢失数据,还是做了一定的兜底措施。