云烟博客 | 网络安全实战经验与个人技术成长笔记

typecho留言墙开发


为 Typecho 开发便利贴留言墙

前言

在浏览过一些师傅们的博客,看到一些让人眼前一亮的留言墙。

每一条留言都是一个独立的小故事,我认为都很有意义。于是,NoteWall 插件诞生了。

开发时遇到很多坑,记录适配PJAX和优化交互体验的一些细节。
[notewall]

查看效果


一、 核心视觉:如何画出一张“便利贴”?

首先是视觉设计。便利贴的灵魂在于“非秩序感”和“纸张质感”。

  1. 随机旋转:现实中的便利贴不可能贴得横平竖直。
  2. 卷角阴影:利用 CSS 的 border-radiusbox-shadow 模拟纸张微微翘起的效果。
  3. 图钉效果:用 CSS 画一个小圆点,配合径向渐变(Radial Gradient)做出金属光泽。

但开发过程中遇到了一个问题:头像变形
很多 Typecho 主题为了响应式,会强制设定 img { width: 100% }。这导致头像被拉伸成了巨大的方块。即使加了 !important 甚至都被主题样式覆盖。

解决方案
我放弃了 <img /> 标签,改用 div 容器配合 background-image。主题的 CSS 只会匹配图片标签,不会影响普通的 div。

/* 核心代码:防拉伸头像容器 */
.note-avatar-bg {
    display: block !important;
    width: 40px !important;    /* 锁死宽度 */
    height: 40px !important;   /* 锁死高度 */
    border-radius: 50% !important;
    /* 关键:使用背景图填充,配合 cover 属性,无论图片比例如何都完美适应 */
    background-size: cover;    
    background-position: center center;
    background-repeat: no-repeat;
    background-color: #e0e0e0;
}

二、 交互体验:赋予便利贴“物理属性”

参考师傅们的留言墙设计了拖拽的效果。

这里没有引入庞大的 jQuery UI 或第三方库,而是用原生 JS 手写了拖拽逻辑。

关键逻辑:

  1. 随机分布:页面加载时,通过 Math.random() 计算每个卡片的 lefttoprotate 角度。
  2. 零延迟拖拽:监听 mousedowntouchstart。为了防止鼠标甩得太快导致“脱手”,移动事件必须绑定在 document 上,而不是元素本身。
// 初始化随机布局片段
for(var i=0; i<notes.length; i++) {
    var n = notes[i];
    // 计算容器内的随机坐标
    var x = Math.random() * (cw - 220);
    var y = Math.random() * (ch - 220);
    // 给它一个 -3度 到 3度 的随机倾斜,看起来更自然
    var r = Math.random() * 6 - 3; 
    
    n.style.left = x + "px";
    n.style.top = y + "px";
    n.style.transform = "rotate(" + r + "deg)";
}

三、 攻克痛点:PJAX 与 QQ 头像

在测试阶段,我遇到了两个 Typecho 插件开发中经典的大坑。

1. PJAX 刷新导致的“堆叠事故”

博客启用了 PJAX 无刷新加载。每次点击页面时,JS 不会重新运行,导致便利贴墙没有进行初始化排版,所有卡片都堆在了左上角 (0,0) 的位置。

解决方法:
引入 MutationObserver 监听器。它就像一个“哨兵”,时刻盯着页面 DOM 的变化。只要发现便利贴墙的容器插入到了页面中,就强制执行排版函数。

// 针对 PJAX 的 MutationObserver 监听
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        if (mutation.addedNodes.length) {
            var container = document.getElementById("note-wall-container");
            // 只要墙出现了,且还没排版过,立即执行!
            if (container && !container.classList.contains("init-done")) {
                initNoteWall();
            }
        }
    });
});
observer.observe(document.body, { childList: true, subtree: true });

2. QQ 邮箱头像不显示

国内访问 Gravatar 并不稳定,且很多用户只有 QQ 头像。
我在 PHP 中增加了一个正则判断:如果是 QQ 邮箱,直接调用腾讯官方的头像接口;否则使用极客族(Geekzu)的高速 CDN 源。

// PHP 后端判断逻辑
if (strpos($mail, '@qq.com') !== false && preg_match('/^\d+@qq\.com$/', $mail)) {
    $qq = str_replace('@qq.com', '', $mail);
    // 腾讯官方接口,稳!
    $avatarUrl = 'https://q1.qlogo.cn/g?b=qq&nk=' . $qq . '&s=100';
} else {
    // 极客族高速源
    $avatarUrl = 'https://sdn.geekzu.org/avatar/' . md5($mail) . '?s=100&d=mm';
}

四、 逻辑升华:关联评论的“画中画”

最初的版本所有留言都是平级的,看不出谁回复了谁。
为了体现“对话感”,我重写了数据库查询逻辑,使用了 SQL 自连接(Self-Left Join)

在查询每一条评论时,顺便去查询它 parent ID 对应的父级评论内容。如果存在父级评论,就在便利贴下方渲染一个半透明的“引用条”。

// Typecho 数据库查询构造
$select = $db->select('c.*', 'p.author as parentAuthor', 'p.text as parentText')
    ->from('table.comments as c')
    // 左连接:把 table.comments 表连一遍,取名为 p (parent)
    ->join('table.comments as p', 'c.parent = p.coid', Typecho_Db::LEFT_JOIN)
    ->where('c.status = ?', 'approved')
    ->limit($limit);

效果如下图所示,回复的评论会带有一个精致的小尾巴,既不破坏整体美感,又保留了上下文逻辑。


从最初简单的 div 堆叠,到解决 CSS 样式冲突,再到适配 PJAX 和 QQ 头像,NoteWall 最终打磨成了一个勉强还能看的 Typecho 插件,后续上传到github开源。

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »