书接上回,由于过年了嘛,开发中断了几天时间,算下来总共花了 10 天的时间搞定这玩意。

后端

开源地址

后端用的 Java,数据库用的 MySQL。Spring Boot + JWT + Mybatis,再加上 Spring Validation 做参数校验,BCrypt 做密码加密,都是些基础的东西,没啥亮点。

前端

开源地址

前端能说的东西就比较多了。刚开始我设想的是先在博客中加一个 id 为特定名称的 div 元素,然后引入一个 SDK,把评论的 DOM 动态添加到这个 div 里,事实上 gitalk 和 Disqus 也是这么做的。这样能写,但是挺麻烦的,还得自己配 vite 环境才能舒服地调试、打包。

所以我偷懒,用了一个 iframe 元素来把评论系统嵌入博客中,直接加载另一个域的 DOM。但是存在一些问题:

  • iframe 元素的高度并不能自动适配内部的高度;
  • iframe 跨域通信问题。

跨域带来的问题

由于浏览器同源策略,iframe 元素的 src 和父 frame 的 location.origin(即 协议、主机、端口元组)如果不一样,则子 frame 和父 frame 相互之间能拿到的信息就非常少。不过 HTML5 引入了 window.postMessage() 方法可以跨域通信,前提是需要知道发送目标的 origin 属性。

这个好办,父 frame 肯定知道子 frame 的 origin,虽然跨域导致不能访问子 frame 的 window,但是 iframe 元素有个 src 属性啊,那不就是子 frame 的 origin 嘛。父 frame 把自己的 origin 发送给子 frame,这样子 frame 也知道了父 frame 的 origin 了,两个 frame 可以正常通信了。

但是如果 postMessage 发送时子 frame 没有加载完成,监听事件就不会触发。所以应该写一个循环,每隔 1 秒发一次,直到收到子 frame 的消息。

动态适配 iframe 元素高度

既然能够通信了,那么子 frame 可以把自己原本的高度(document.body.scrollHeight)发给父 frame,父 frame 再根据子 frame 发的消息更改 iframe 元素的高度。由于子 frame 高度会变化,所以应该一直循环发送。

总的来说就是父 frame 一直循环向子 frame 发送自己的 origin,子 frame 收到消息后开始循环给父 frame 发送自己的高度,父 frame 收到消息后终止循环,并修改 iframe 元素的高度。

demo

按照惯例来写一个小 demo。

这是父 frame:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    father
    <iframe id="commentsFrame" src="<url to son frame>" style="width: 100%"></iframe>
    <script>
        const commentsFrame = document.getElementById('commentsFrame')
        let rec = false
        window.addEventListener('message', (e) => {
            rec = true
            commentsFrame.style.height = e.data + 50 + 'px'
        })
        function send() {
            if (rec) {
                return
            }
            window.frames[0].postMessage(location.origin, commentsFrame.src)
            setTimeout(send, 1000)
        }
        setTimeout(send, 1000)
    </script>
</body>
</html>

这是子 frame:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="con">
        <button onclick="add()" style="width: 100vw;">add</button>
        <p>son</p>
    </div>
    <script>
        let host = ''
        function loop() {
            window.parent.postMessage(document.body.scrollHeight, host)
            setTimeout(loop, 200)
        }
        window.addEventListener('message', (e) => {
            host = e.data
            loop()
        })
        const a = document.getElementById('con')
        function add() {
            a.innerHTML += '<p>son</p>'
        }
    </script>
</body>
</html>

可以发现,连续点击子 frame 的按钮,iframe 元素的高度确实会随着子 frame 的高度变化。

2023-01-27