最近再做js编辑器, 发现编辑的事件是个课题

有可能使用的事件
  • key
    • keydown 最先被触发
    • keypress 不会被中文触发. 输入了文字, 但是, 文字还没有上去.
    • keyup 最后触发, 文字上去了已经.
  • observer
  • change
  • input
实际测试key事件
  • onkeyup, 其他ok,
    • tab依旧会跳走
    • enter, 会莫名加div(顽固的执行系统原有策略)
    • delete/backspace会删除一个字符然后再执行后面的脚本.
  • onkeydown
    • 这里才是处理/s的地方, tab, enter, delete/backspace全都正常.
    • 中文会触发, 我们只要不理他就好了.
    • 很神奇, 我试验出来, space不响应这个触发. 应该还是处理的消耗问题, timeout可以解决, 参见: 2020-04-08
    • 很神奇, 睡了一觉之后, 这个问题不见了, 果然睡眠改变命运.
  • onkeypress
    • 如果没有e.preventDefault();那么顺序是: keydown -> keypress -> keyup
    • 如果做了e.preventDefault(); 那么顺序是: keydown -> keyup, 这里keypress不会被触发.
    • 这个是即将废弃的事件mdn预告了
    • 这个很多时候不能呼出的原因是浏览器纷纷有自己不支持的按键了, 各种按键都不能触发keypress了.
要忽略输入法这么做: (mdn都有答案了!!!!)
  • https://www.fxsitecompat.com/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/
//https://developer.mozilla.org/en-US/docs/Web/Events/keydown
if (event.isComposing || event.keyCode === 229) {
    return; //这样就可以忽略输入法的中间输入了.
}
键值的区别
  • keydown and keyup provide a code indicating which key is pressed, while keypress indicates which character was entered. For example, a lowercase “a” will be reported as 65 by keydown and keyup, but as 97 by keypress. An uppercase “A” is reported as 65 by all events.
空格
  • 229代表输入法在处理这个按键.
  • 只有safari有这个问题, 并且要开输入法才有. 英文状态没有问题.
  • chrome没有这个问题.
  • keydown会出现229, 如果是中文输入法. 这个表现其实大部分情况是完美的, 毕竟用户在选字的时候, 要的不是真的输入.
  • keyup不论什么输入法, 都会是正常的code.
  • 因此, 空格确实需要keyup, 这个结论错误, mdn有样例代码. 上面有.
tab

参见隔壁blog: 2019-02-27editable之后tab的处理


[ contenteditable="true"] {
    /* 使用属性编辑器	 */
    border: 1px solid rgb(155, 72, 72, 0.5);
    background: rgba(201, 167, 123, 0.555);
    height: 80%;
    display: inline-block;
    /* 这个会导致不自动生成div啥的. 但是, 会有br*/
    width: 80%;
    /*  这一条需要加进来, 因为display: inline-block;会导致整个块缩起来 */
    margin: auto;
    padding: 10px;
    -moz-tab-size: 40;
    -o-tab-size: 40;
    tab-size: 40;
    white-space: pre-wrap; /*tab的关键性代码*/
}
testd.onkeydown = function (e) {
    if (e.keyCode == 9) {
        e.preventDefault();
        document.execCommand('indent', false, null)
        document.execCommand('insertText', false, 'xx	ss	' + e.code + "| |t\tt");
    }
}

键盘事件总结

keyboardevent顺序
  • https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
  • 一定要看英文版. 比如按键顺序:
  1. keydown
  2. keypress 这个已经是不建议使用的事件了
  3. keydown
  4. keypress
  5. «repeating until the user releases the key»
  6. `keyup
键盘事件携带的属性code, keycode, key……
  • keycode: 这个是最主要使用的. 键的值. 如果是输入法正在输入, 这里会得到’229’, 否则会得到对应的键值, 比如tab对应9.
  • key: 键的字面值, 一个字符串, 比如’f’
  • charcode: 这个有点奇怪, 一般都是0? 因为我是再keydown阶段看的.
    • 这个值也已经不推荐使用了.
    • 这个值再keypress时发生作用.
    • 他本身如果起作用的话, 和keycode是一样的值, 我看到0, 是因为他真的没生效.
  • code: 字符的字面值, 一个字符串, 比如: ‘keyF’
  • 修饰键:
    • altkey 布尔值
    • ctrlkey 布尔值
    • shiftkey 布尔值
    • metakey 布尔值
  • inputmethod输入法
    • composed 一个布尔值 标明这个事件是否可以从shadow dom传递到dom. 这里出现了一个关键字shadow dom. 我们稍后研究一下.
    • iscomposing 一个布尔值, 如果他是true, 那么代表他在compositionstart和compositionend之间.
compositionevent
  • 这个事件是和输入法紧密关联的.
  • 只要是文本构成系统(最有代表性的就是输入法了)发生作用的时候, 这一类事件就会生效.
  • CompositionEvent
  • compositionstart, compositionupdate, compositionend, 这是三个阶段性事件.

至此, 我们已经可以很彻底的理解下面的代码:

editor.onkeydown = editinput;
function editinput(e) {
    if (e.isComposing || e.keyCode === 229) {
        return; //这样就可以忽略输入法的中间输入了. 使用input事件时这个并不生效
    }
    if (e.keyCode == 9) {
        e.preventDefault();            
        document.execCommand('insertText', false, '\t');
        //很神奇这里必须用转义符号, 直接贴入一个tab是不行的, 会自动当做空格处理.
    }
}

特别提示一下, 虽然key类事件的顺序是公认的, 但是, 如果加上input和observer那么就还有些分歧, 目前w3c的讨论组还在讨论中.

input事件

input

  • https://developer.mozilla.org/en-US/docs/Web/Events/input
  • 这个事件等于change事件, 不过他是在editable元素上触发.
  • editable没有change事件, 只有input事件, 确切的说是change事件不一定每次都被触发.
  • input的触发没有问题, 但是: 没有keycode, key, charcode, code这些.
  • 也就是说input没有判断输入法输入的能力????

beforeinput

  • 在input被真正改变之前触发
  • https://developer.mozilla.org/en-US/docs/Web/Events/beforeinput
  • 这个依旧不可以, 并且这个不可以用onbeforeinput
editor.addEventListener("beforeinput", editinput); //可以这样注册事件.
function editinput(e) { 
    alert('onbefore');
}
  • input和key事件的混用, google的chrome工程师mathias推荐尽量用input, 可以判断一下使用, 那么我的问题就是, input能服务输入法吗?

陈词总结: 如果要用input事件, 那么就要去写composition事件. addEventListener方式就可以写.

反思了一下, 不能半途而废, 我要把input写好.

仔细想了一下, input和observer貌似无法处理类似tab键这样的事???????

observer 事件 这篇blog太长了, 这个单独开个题吧: 2019-03-19mutationobserver

  • https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
  • object.observer已经是被淘汰的接口, 目前的建议是: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
  • mutationobserver的处理优先级相当高, 早于正常的ui事件, 因此可以让整个处理更加流畅. Vue用了这个.

这里出现了一个小小的分支.

如何查看dom树
  • chrome的elements视图就是dom树的视图.
  • https://developers.google.com/web/tools/chrome-devtools/
  • element这边右键可以选择break on, 然后相关事件发生就会被break,
    • 这个是发生在mutationobserver
    • 这个会显示在devtools->elements->dom breakpoints
  • source那边也可以针对某个event进行breakpoints

事件这件事本身就值得研究一下

  1. 入门的事件文档: https://zhuanlan.zhihu.com/p/24620643
  2. 执行队列请控制后, js就到事件队列去找任务.
  3. 微任务排在宏任务之前,
    1. micro包括
      1. promise.then和
      2. mutationobserver.
      3. process.nextTick 优先级高于Promise, 这是一个node概念.
    2. macro包括
      1. timeout和
      2. 1/o
      3. 以及交互事件
    3. https://zhuanlan.zhihu.com/p/34229323
      1. 这个讲的比较清晰, 而是是按照w3c/what标准对译的, 用词比较准确.
      2. 而且他解释了vue使用mutationobserver的原因和方法.
  4. 疑问出现了: 可以自定义一个listener吗? 可以自定义一个event吗?
var event = new Event('build');
// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);
 // Dispatch the event.
elem.dispatchEvent(event);
// custom event 也可以用来新建事件
var event = new CustomEvent('build', { 'detail': elem.dataset.time });

官方一个完整的示例:

<form>
  <textarea></textarea>
</form>
const form = document.querySelector('form');
const textarea = document.querySelector('textarea');

// Create a new event, allow bubbling, and provide any data you want to pass to the "details" property
const eventAwesome = new CustomEvent('awesome', {
  bubbles: true,
  detail: { text: () => textarea.value }
});

// The form element listens for the custom "awesome" event and then consoles the output of the passed text() method
form.addEventListener('awesome', e => console.log(e.detail.text()));

// As the user types, the textarea inside the form dispatches/triggers the event to fire, and uses itself as the starting point
textarea.addEventListener('input', e => e.target.dispatchEvent(eventAwesome));


//甚至可以这么写:
form.addEventListener('awesome', e => console.log(e.detail.text()));
//这时候awesome还没创建呢.

textarea.addEventListener('input', function() {
  // Create and dispatch/trigger an event on the fly
  // Note: Optionally, we've also leveraged the "function expression" (instead of the "arrow function expression") so "this" will represent the element
  this.dispatchEvent(new CustomEvent('awesome', { bubbles: true, detail: { text: () => textarea.value } }))
});
  • 这里写的比mdn更清晰.
    • https://javascript.info/dispatch-events
    • https://www.sitepoint.com/javascript-custom-events/
  • 要点 Event.preventDefault() 被调用的情况下. target.dispatchEvent会返回false. 注意这里还需要cancelable=true. 示例:
var div = document.getElementById("test");

div.addEventListener("custom", function(e) {
  e.preventDefault();
});

div.innerHTML = div.dispatchEvent(new CustomEvent("custom", {
    cancelable: true
}));

pre值得研究一下 后续blog 2019-3-19-pre

这个标签是三兄弟

  • code 显示代码用的, 如果是多行代码, 建议放到pre里面.
  • pre 其实就他最特殊, 显示很特殊.
  • samp 展示输出用的, 可以嵌套kbd(代表用户输入).

shadow dom

  • dom本身到了element就是原子了, shadow dom可以描述一个element.
  • 举个例子就是video元素, 再里面有各种按钮和操作, 这都是shadow dom发挥作用的地方.
  • shadow dom用一棵树来描述一个element.
  • 这个element自身包含了code, css, js….也就是说, 形成了一个html闭包.
  • shadow dom的本质是封装.
  • shadow dom是custom elements的一部分.
  • attach的时候选择open还是close?
    • https://blog.revillweb.com/open-vs-closed-shadow-dom-9f3d7427d1af
    • 其实很无聊, open模式是可以用shadowroot属性引用, close只能自己保留一个(attach)返回值引用.
  • 这里介绍了更多关于: https://glazkov.com/2011/01/14/what-the-heck-is-shadow-dom/
custom element
  • 自定义标签终于形成了完整的规范.
  • https://developer.mozilla.org/en-US/docs/Web/Web_Components
  • 整个标准叫web components, 它包含三部分:
    • custom element, 一组js api, 可以自定义标签以及他的hebaviour,
    • shadow dom, 一组js api, 可以定义一个元素的内部结构.
    • html templates, html的模板标签, 可以在上面两个api里面重复使用.
  • 基础做法
    • 定义一个类或者函数.
    • 注册这个custom element, 用 customelementregistry.define.
    • 如果需要加入这个元素的shadow dom, 用element.attachshadow. 加入子元素, 时间监听event listener, 这些都可以用dom的标准方法做.
    • 如果需要定义html模板, 使用
    • 就像正常的html标签一样使用自定义标签.
functional 的定制元素
  • https://github.com/hybridsjs/hybrids
  • 为了简化自定义元素这件事, 大家开发出各种框架.