From 68d4b87f8ef8097fe915276f4fe8c3c448e275fc Mon Sep 17 00:00:00 2001 From: Eltrac Date: Tue, 23 Sep 2025 14:03:49 +0800 Subject: docs: new draft Former-commit-id: 53ff2cf44465209fc186eaea10396fd17d7025e1 --- ...274\200\345\217\221\346\227\245\345\277\227.md" | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 "content/posts/libra-js-\345\274\200\345\217\221\346\227\245\345\277\227.md" (limited to 'content/posts') diff --git "a/content/posts/libra-js-\345\274\200\345\217\221\346\227\245\345\277\227.md" "b/content/posts/libra-js-\345\274\200\345\217\221\346\227\245\345\277\227.md" new file mode 100644 index 00000000..9ebbd793 --- /dev/null +++ "b/content/posts/libra-js-\345\274\200\345\217\221\346\227\245\345\277\227.md" @@ -0,0 +1,77 @@ +--- +title: Libra.js 开发日志 +tags: + - Web + - Geek +date: 2025-09-23T12:58:00 +draft: true +toc: true +--- + +博客从去年十二月开始就一直在用 [Fancybox](https://fancyapps.com/fancybox/) 作为博客的图片灯箱插件,之前还在用 Typecho 搭建博客的时候就在用这个库。所谓灯箱,其实就是点击图片之后将图片放大并居中显示,就像现实世界中的广告灯牌一样。不过我其实一直很想要把 Fancybox 换掉,因为它太臃肿了,包含了很多我不需要的功能(比如 1:1 放大、镜像翻转、前后切换等等),体积超过 100 KB;不仅如此,Fancybox 实际上是一个商业库,并不是免费开源的。出于这些原因,我决定自己从零开始做一个灯箱插件。 + +## 原则和技术选型 + +我希望项目能做到足够「轻」,一方面是打包后要足够小,另一方面是运行时不需要消耗太多资源。权衡之后,还有一个点就是必要的配置项足够少,最好能做到零配置起步。我决定用原生 JavaScript 编写,毕竟需求没有很复杂。由于需求本身足够简单,除了开发时会用到的一些依赖,打包后的库不应该包含或依赖于任何第三方库,这也能避免不必要的维护工作。此外,目前我能找到的灯箱插件大多都包含了多图排版功能或者额外的图片缩放操作,我希望这个库能够专注于需求本身,只要把图片放大就好了。 + +轻量、原生、专一,~~非常适合用作软件的宣传噱头的~~三个基调就这么定好了! + +## 模块设计 + +需求只有一个,点击图片后播放动画并放大以供查看,再明确不过了,完全可以跳过需求分析的步骤,直接开始设计。 + +由于点击图片之后需要播放动画——原图片从原位置、原大小逐渐过渡到屏幕正中间、放大显示的状态——而图片 `` 本身是一个行内元素或块元素,要让其「飞离」容器会有些困难,所以我的想法是构造一个**影子元素**(Shadow),通过绝对定位覆盖在图片上方,由于影子元素是绝对定位的,要放大和位移就很方便了。 + +除了放在正中央的图片,要组成灯箱,往往还需要**遮罩**(Overlay)。遮罩平时隐藏,打开图片灯箱之后显示在其他元素和影子图片之间,这样在视觉上,打开灯箱之后用户就只能看到放大的图片,可以起到突出强调的作用。 + +不需要其他花里胡哨的功能的话,**灯箱**(LightBox)就可以理解为「影子 + 遮罩」。影子的生命周期(创建和销毁)由自己管理,遮罩也用一个单独的模块管理,灯箱负责调用这两个模块,控制灯箱的开启和关闭,也负责把对应的图片信息传递给它们。 + +在之后的开发过程中,我发现影子模块的代码有些复杂,因为播放动画的逻辑也写在里面了,于是把动画相关的代码分离了出来,单独作为**动画**(Animation)模块。 + +于是四个模块的分工就很明确了: + +- Shadow:管理影子元素的生命周期;获取图片的位置和尺寸信息。 +- Overlay:创建、打开和关闭遮罩。 +- LightBox:初始化监听器;调用 Shadow 和 Overlay 两个模块来打开灯箱。[^1] +- Animation:计算动画的起始和结束状态,并应用相关属性。 + +## 定位图片时踩的坑 + +一开始我在一两个小时内就实现了需求,在后续测试时却发现了明显的问题。我在测试页面里能够正常开关灯箱,图片也能被放到正确的位置,但放到我的博客上时却发现动画的起始位置完全不对,图片不是从原始位置放大的,而是从页面最下方瞬间飞上来的,而且飞上来之后的位置也有偏移。 + +后来发现原因在于定位时使用的属性不对,我是这样获取图片的初始位置的: + +```js +// shadow 是根据图片创建的影子图片 +// image 是原图 +shadow.style.top = image.offsetTop; +shadow.style.left = image.offsetLeft; +``` + +一般情况下,`offsetTop` 和 `offsetLeft` 会返回元素相对于 `` 的位置,`offsetTop` 是元素顶部距离 `` 顶部的距离,`offsetLeft` 同理。然而,这并不是 `offset` 系列属性的用途,实际上,它们返回的是相对于最近的**定位祖先元素**(positioned ancestor element)的距离。[^2]其中,祖先元素就是父元素的父元素…… DOM 树上比自己更高层且有属于其枝干的节点;定位(positioned)指的是 CSS `position` 属性不为 `static`。如果没有这样的祖先元素,就会选择 `` 作为参考对象。 + +问题就出在这里,如果图片位于一个定位元素(positioned element)中,那定位就不是相对于 `` 的;然而,影子元素的定位方式是 `position: absolute`,它在被创建时是插入到 `` 的末尾的,也就是说,影子元素是相对于 `` 进行定位的。由于计算得出的位置和实际应用的位置,两者的参考系不同,最终定位的结果自然是错位的。知道原因之后,解决方案就很简单了,只要获取原图相对于 `` 的位置信息就好。 + +…… + +```js +const rect = this.original.getBoundingClientRect(); +this.originalPosition = { + top: rect.top + window.scrollY + config.offset.y, + left: rect.left + window.scrollX + config.offset.x, + width: rect.width, + height: rect.height, +}; +``` + +## 绘制动画时踩的坑 + +`requestAnimationFrame()` 和 CSS transition + +## JavaScript 设置样式时踩的坑 + +没写单位啊! + +[^1]: 其实 LightBox 模块还能拆开来,初始化监听器和开关灯箱明显是两个职责,但我担心分出多个模块会让结构变得复杂。 + +[^2]: 参见:"The offsetTop read-only property of the HTMLElement interface returns the distance from the outer border of the current element (including its margin) to the top padding edge of the offsetParent, the closest positioned ancestor element." —— [HTMLElement: offsetTop property - Web APIs \| MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop#:~:text=The%20offsetTop%20read%2Donly%20property%20of%20the%20HTMLElement%20interface%20returns%20the%20distance%20from%20the%20outer%20border%20of%20the%20current%20element%20(including%20its%20margin)%20to%20the%20top%20padding%20edge%20of%20the%20offsetParent%2C%20the%20closest%20positioned%20ancestor%20element.) -- cgit v1.2.3