WEB 编程:富文本编辑器 Quill 配合 Pico.css 样式被影响的问题之Shadow DOM

发布于:2024-10-11 ⋅ 阅读:(24) ⋅ 点赞:(0)

前情提要

前面我写过一篇文章讲这个事情。用的是 iframe 的方法。文章链接:

WEB 编程:富文本编辑器 Quill 配合 Pico.css 样式被影响的问题-CSDN博客

问题来了

使用 iframe 确实可以在框架页面有 Pico.css 的情况下,在 iframe 里面的 Quill Editor 不受影响。但是,编辑器里面用户输入的内容,是需要提交到服务器端的。因此,编辑器是嵌套在一个 <form> <div id="MyEditor"></form> 里面的。同时这个 Form 里面还要有一个提交按钮。

如果仅仅是提交编辑器里面的内容,那这样做就没有问题。但是,可能还有单独的文章表,等等其它需要用户输入的东西,要一起提交。比如文章标题:<input type="text" id="MySubject">, 而这个输入框是需要被 Pico.css 给予样式的。如果把这些输入框和 Editor 一起放进 iframe 里面,则无法接受 Pico.css 的样式!

采用 Shadow DOM 来解决这个问题

Shoadow DOM 的基本概念

页面上很多元素,输入框,按钮,等等,构成页面的 DOM。

页面上还可以有 Shadow DOM,挂载在 DOM 的某个节点底下。在 Shadow DOM 里面的页面元素比如一个<input type="text"> 或者一个按钮比如 <button> 也是正常显示在页面上的,但是,这些元素不受页面上的 CSS 的影响。相当于屏蔽掉了页面上的 CSS;

具体操作

思路

我们是把 Editor 放到 <div id="MyEditor"></div> 里面的。我们把这个部分,放进 Shadow DOM;

然后在页面,也就是 Shadow DOM 外面,有一个表单,表单内部有提交按钮,以及文章标题输入框。那么,这些,当提交按钮被点击,就可以提交表单内容。

问题

如果把表单(<form>)放到 Shadow DOM 外面,那么,提交按钮如何获取到在 Shadow DOM 里面的 MyEditor 的用户输入的内容?

如果把一个页面元素比如 quill Editor 放进 Shadow DOM

首先,把 quill Editor 相关的 HTML 代码,放进 HTML 模板。这个模板的内容加载到浏览器里面后,浏览器是不会显示的。模板的代码如下:

<template id="my-element">
  <!-- 用 template 来封装这个编辑器,避免它被页面上的其它 CSS 污染 -->
  <!-- quill 编辑器的封装 https://quilljs.com/ -->
  <script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js">
  </script>
  <link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css"
  rel="stylesheet">
  <style>
    .edit_container { font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
    text-align: center; color: #2c3e50; margin-top: 60px; } .ql-editor{ height:400px;
    }
  </style>
  <div id="editor1">
    <h2>
      这是编辑框里面的内容,服务器端加载内容可以直接填到这里。
      <#AContent>
        这里替换
    </h2>
  </div>
  <script>
    const MyContainer = host.shadowRoot.querySelector("#editor1");

    const quill = new Quill(MyContainer, {
      modules: {
        toolbar: [[{
          header: [1, 2, false]
        }], ['bold', 'italic', 'underline'], ['image', 'code-block'], ],
      },
      theme: 'snow'
    });
  </script>
</template>

其实就是用 <template > 这个标记来把一个 HTML 的网页元素包起来,就是一个模板了。

使用模板在页面里真正创建和显示这个元素

            <div id="MyHost">
            </div>

     <script>
            const host = document.querySelector("#MyHost");
            const shadow = host.attachShadow({
              mode: "open"
            });

            const template = document.getElementById("my-element");

            shadow.appendChild(template.content);

    </script>

首先定义一个容器,MyHost 然后用代码,把模板里面的 HTML 代码描述的 HTML 元件挂在这个 MyHost 的底下,作为它的 Shadow DOM。如果页面显示正常,打开浏览器的开发者工具,查看页面元素,会看到开发者工具里面,在 MyHost 那个 DIV 底下,会显示【shadow-root】的字样,再底下才是这个 Editor 的代码。

解决提交的问题

表单(<form>)和提交按钮,放在页面里面,而不是 Shadow DOM 里面。这样按钮才能获得 PICO.CSS 提供的样式。同样需要提交的文章的标题栏,也在这个表单里面,也能够获得样式。

但是,Editor 不在这个表单里面。如果把表单和 Editor 放在一起,那就必须把标题输入框和按钮也和 Editor 放一起,那这两个玩意就无法获得页面上的 PICO.CSS 提供的样式了。

因此,我要用 JavaScript 代码,从 Shadow DOM 里面的 Editor 获得用户输入的数据,然后给外面的表单提交。代码如下:

       <form id="myForm" action="ContentUpdate" method="post">
          <input type="hidden" name="delta" id="deltaInput">
          <input type="hidden" name="html" id="htmlInput">
          <input type="text" name="MySubject1" id="MySubject1" value="我的标题">
          <input type="submit" value="提交">
        </form>
          
        <script>
            const host = document.querySelector("#MyHost");
            const shadow = host.attachShadow({
              mode: "open"
            });

            const template = document.getElementById("my-element");

            //把页面模板放到容器的 Shadow DOM 里面去。
            shadow.appendChild(template.content);

            //监听提交按钮,把提交内容复制到隐藏字段里面再提交,服务器读隐藏字段值
            const MyEditor = host.shadowRoot.querySelector('#editor1');

            //if not MyEditor alert("没找到编辑");
            //const quill = host.quillInstance;
            document.getElementById('myForm').addEventListener('submit',
            function() {
              // 获取 Quill 编辑器的内容
              const delta = JSON.stringify(quill.getContents());
              const html = quill.root.innerHTML;

              //delta = '';
              //html = MyEditor.outerHTML;
              // 将内容放入隐藏输入框中
              document.getElementById('deltaInput').value = delta;
              document.getElementById('htmlInput').value = html;
            });
          </script>

注意,上述代码里面的 quill 是在 Editor 的模板文件里面声明和创建实例的。这里仅仅是调用它。

煞科

到这里,整个页面就做好了。实现了:

  1. 代码封装;把 Editor 单独放到一个文件里面,加上 Template 套起来。
  2. CSS 隔离:把 Editor 放到 Shadow DOM 里面,防止被页面的 CSS 污染。
  3. 提交:用 JavaScript 把用户在 Editor 里面输入的内容提取出来,放到一个表单内部的隐藏字段里面,就可以正确提交了。一个在 Shadow DOM 里面创建的 Editor 对象,只要创建时对象是一个 JavaScript 的全局变量,在其它地方也是可以调用到的。

吐个槽

前端代码的语法真是狗屎。像 CSS 这种完全没有作用域和封装的概念,需要用 Shadow DOM 或者 iframe 之类的技巧来避开语法的坑,完全属于奇巧淫技的做法,属于野路子,非正常写代码的路子。真是可怜成天专门写前端代码的童子。

追加

把 Quill editor 放进 Shadow DOM,样式被污染的问题倒是解决了。但是,发现在使用的时候,不如选择了一行字,点击它自带的工具栏上的【粗体】按钮,这行字不会变粗。同时光标跳到文章的最前面。我以为是页面代码哪里写得不对导致,浪费2小时检查半天来回测试。最后发现同样页面代码,只要不是放进 Shadow DOM 这个问题就部存在。

上网一搜索,很多老外在提这个问题,从 N 多年前就提,从 v1 提到 v2,这个问题依然没解决。而且提问题的人里面,有人分析了问题原因,就是当它在 Shadow DOM 里面时,选中的文字的位置和长度值,都读不到。之所以有那么多人提这个问题,说明有那么多人想要把它放进 Shadow DOM。为啥呢?我估计多半也是因为想要避开 CSS 污染。

其实,这个问题,是一个说明前端开发非常混乱的非常典型的例子。语言设计的弱智,导致各种绕路的野路子,导致各种奇怪问题。很多问题非常难以解决。即便要解决,也要绕圈子,也很浪费时间。结论就是:如果要使用 Quill Editor 的话,不要试图把它放进 Shadow DOM。

各位,看完本文如果觉得有点干货内容,点个赞吧。