2020-04-17 15:53
最近在用Vue做一些小东西。做了一个网页游戏,没做完所以还没放出来。还做了一个小工具,让文字排列在直线和斜线上,用来发酷炫的微博。在微博上宣传了一下,似乎反响很好。还生平第一次收到了别人提交的issue(解决了)。
这个微博文字排列工具功能不复杂。用Vue因为想学Vue。
要实现的功能包括:
踩过的坑有:
开始时对预览和固定文字用了不同的数据源。每个格子里有两个<span>
,分别绑定到两个数组。一个Overlay数组在鼠标拖动时更新预览文字,随时刷新。一个Canvas数组只在鼠标抬起时更新固定化的文字,不再刷新,但保留操作记录以支持逐步撤销。后来改版时合并为一个数组了。预览文字就是固定化的文字,鼠标拖动需要更新预览时,撤销上一步,重做预览。固定化就是将当前预览的路径标记为不可更新。感觉这样少了一重数据绑定,无论空间占用还是计算量都会减少。
微博表情符号就需要另一个Emoji数组。这里的Emoji不是指Emoji字符,微博表情符号是形如“[微笑]”这样的占位符,对应的是一个png图片。在识别到一个占位符之后,将其字符串本身当做一个完整的单位写入Canvas数组,让它占据一格的位置。同时将对应的图片文件路径放入Emoji数组。在html这边,如果格子对应的Emoji数组值不为空,则隐藏文字,显示图片。
而Emoji字符是另一个概念,是指类似🌷、🎁、💩、😜、👍这样的字符。它们是字符不是图片,直接当作文字处理就好。但是在JavaScript下,这些字符的长度是2,也就是说“🌷”.length === 2
这样。因而每个字符都会被拆分到两个格子里,显示为乱码。就像这样:
{ width=”100%” }
解决方法是:不使用charAt()
读取下一个字符,而是用codePointAt()
,这样获取的字符code会正确识别这些Emoji字符。然后再用String.fromCodePoint()
转回字符串。完整的代码是这样:
getChar: function(i) {
if (this.textTrimmed.charAt(i) === "[") {
var j = this.textTrimmed.indexOf("]", i);
if (j > i) {
// 这里的emoji是指微博表情,不是Emoji字符。
var emojiText = this.textTrimmed.slice(i, j + 1);
var emoji = this.getEmoji(emojiText); // 如果未找到,这里返回undefined。
if (emoji) {
return emoji.text;
}
}
}
// 下面这行不兼容Emoji字符。
// return this.textTrimmed.charAt(i);
// 下面这行是兼容Emoji字符的读取方法。
return String.fromCodePoint(this.textTrimmed.codePointAt(i));
}
插入表情符号时需要读取光标位置。本来JQuery可以做,但想看一下Vue是怎么做的。首先Vue里获取DOM是通过$refs。在textarea的tag上定义ref="textarea"
,然后在Vue里用var textArea = this.$refs.textarea;
就可以获取这个DOM。然后用selectionStart
和selectionEnd
读取光标位置(这样好像有浏览器兼容性问题,但实际上由于另外的原因,网页在IE和Edge上已经崩了,所以兼容性问题就留给以后一次性解决)。
在光标位置插入表情之后又出现新的问题:由于修改了绑定的数据,textarea的光标自动跑到文字起始位置去了,这样就无法在同一个位置连续插入表情。所以还要重设selectionStart
和selectionEnd
的值,将光标调整回来。但是刚开始时发现重设的值不起作用,光标还是在起始位置。研究了一下发现两个原因:
textArea.focus()
获取焦点让位置调整生效,原因不明。var textArea = this.$refs.textarea;
var cursorStart = textArea.selectionStart;
var cursorEnd = textArea.selectionEnd;
this.textInput = this.textInput.substring(0, cursorStart) + emojiText + this.textInput.substring(cursorEnd, this.textInput.length);
textArea.focus();
textArea.selectionStart = cursorStart + emojiText.length;
textArea.selectionEnd = cursorStart + emojiText.length;
后三行调整光标位置的指令需要放在一个Callback里,等数据更新完成之后再执行。网上有人是设了一个setTimeout()
,10毫秒之后调整光标,简单粗暴,实测也行得通。但其实Vue提供了一个叫$nextTick()的方法,用法是这样:
var textArea = this.$refs.textarea;
var cursorStart = textArea.selectionStart;
var cursorEnd = textArea.selectionEnd;
this.textInput = this.textInput.substring(0, cursorStart) + emojiText + this.textInput.substring(cursorEnd, this.textInput.length);
this.$nextTick(() => {
textArea.focus();
textArea.selectionStart = cursorStart + emojiText.length;
textArea.selectionEnd = cursorStart + emojiText.length;
});
而Vue获取鼠标事件的方法是在tag上绑定v-on:mousedown.left="..."
这样。
之后的更新会尝试在IE和Edge上修复。此外还考虑支持手机端操作。手机端无法监听鼠标按下、拖动、松开等事件了,而是要处理触摸屏相关的事件,而且好像Android和iOS还不一样。UI方面要大改。愁,没时间又懒,慢慢来吧。