这几年疫情反复不断,距离上一次我拿起相机甚至可以追溯到两年前,实在是泪目。既然不能出去拍照,那只能继续宅着敲代码度日了,于是就有了这个在线相册的小项目,用来方便自己放上一些觉得还不错的摄影作品,当然,也可以记录与展示一些生活照片。
这是一个纯前端的项目,不需要开发后端,没有数据库,只需要把照片丢进去,Git提交一下站点就出来了,就很简单。这篇文章中我会介绍整个项目的开发历程,看完你将收获:
- 一个 Vue3 + Node 快速生成漂亮的在线相册的项目
- 实现瀑布流、懒加载、Node解码图片、缩略图生成、元数据读取、提取颜色等技巧
- 快速集成一个音乐播放器,同步网易云歌单
仓库地址:https://github.com/palxiao/fast-album
仓库开启了 Pages 功能才可以在线访问,我们可以使用 Github 或 Gitee 的 Pages 服务。
个人体验下来 Github 的访问速度比码云更快,当然有被墙的风险,我是绑了自己的域名并加了一层腾讯云免费CDN,应该还是比较稳定的。另外码云不会自动更新网站,要手动进仓库操作,并且还不能自定义域名,居然要收钱。。关键访问速度本就一般。。小了呀。
项目运行起来后大概是这个样子:
网速慢图片多挂着不管就行,提交好了Github会自动部署,部署完毕commit记录旁边会有个绿色的打钩,网站就可以访问了~
点此查看我的在线相册:m.palxp.com (手机访问效果最佳)
接下来我将从零开始,讲解我是如何一步步开发完成这个小项目的,Let's Go~
开始前,我们需要做点微小的工作,项目结构不复杂,根目录view为前端工程目录,我们先在view目录中创建一个 Vite + Vue3 的项目,即便你没有接触过Vue3,访问Vite或Vue官网看看文档应该很快就能创建一个基础项目了。
好了,现在把照片放在resources目录,后续我们只要在电脑上对这个目录中图片进行管理,重新运行即可更新在线相册,这也就不必开发后台管理照片了。
首先创建一个文件使用NodeJs来遍历resources,我们要获得路径名称、图片大小等有用信息(使用 image-size 这个库可以快速获取图片大小),然后输出成JSON文件保存,最后将图片复制到目录中:
运行,这样我们就可以开始编写前端项目了,把JSON文件引入即是图片列表数组,为了摆放好这些图片,我们先来写写图片列表的样式布局。
瀑布流是一种经典的图片排列模式,其特点是等宽不等高,在保持原图比例下显示布局。
目前并没有纯CSS可以完美实现的瀑布流方法,常见的多列布局(multi-columns)实际效果非常差强人意,它会得到如下的一个竖排布局效果,在实际应用中会感觉图片是乱序排列的。
其实用JS实现瀑布流也并不难,我们使用绝对定位布局,由于图片的宽度是平均分布的,只需要计算出图片的高度及、定位对应设置到图片元素上即可:
我们用变量表示有多少列,表示图片间隔,容器总宽度可以由当前的DOM往父级查询来获取,那么图片在布局中的宽高以及值就可以计算出来了,而高度则用一个数组来储存,有多少列就往里存多少个元素,随着图片列表的循环一直累加取出计算就可以得到每张图片的定位了,简单几行代码搞定:
这时候虽然瀑布流的样式已经出来了,但拉到列表底部就会发现瘸腿了:
经过秒的思索,我马上发现了问题所在,上面的代码仅仅只是把图片按左右的顺序依次往下排列,而每张图片高度是不一样的,这就导致出现尾部空白的现象,解决的办法也很简单,每次找出最短的那一列来插入图片即可,我们已经将高度都存在了这个数组中,通过往传入解构数组得到最小值,再用得到下标,就可以知道下一张图片该插入哪一列了,修改上面方法中的最后一行代码:
以上就是实现瀑布流排版的全部核心代码,可以根据实际情况进行扩展,比如通过监听窗口宽度变化动态改变列数量重新排列等。
这个图片排版样式比较少见,但同样是等比例显示图片,区别于瀑布流的是图片不等宽,但同一行图片等高,所以我把它命名为书架流,看着是不是很像整齐排列在书架上的书本:
虽然看起来似乎是等高不等宽,但实际上每行高度并不都是一样的,因此我们需要一个阈值来决定每行高度可以被允许的上限,与瀑布流一样的是,列表整体宽度是已知的,所以核心是计算每行图片的高度,我们先来看看如何实现这个算法。
抽象问题用简单的数学问题描述往往更容易解决。假设某行存在2张图片,已知的实际宽高分别为、和、,而在列表中的相对宽高我们则设为、和、,接着设列表总体宽高为和,已知的为列表父级div的宽度,我们的目的就是求这个的值。
此时由于在同一行中图片等高,于是有:==
又因为图片的比例不变,于是有:/ = /,代入上面的式子可得:
/ = /(同理得到另一个式子:/ = /)
所以上面推导的两个式子可以得出图片在行内的宽度分别为:
而总体宽度为图片宽度相加:
代入可得到:
到这里我们已经可以轻松推导出计算高度的方法了:
上面推导过程我是在纸上完成的,回到代码中,我们可以使用递归来操作图片数组,得到一组计算好宽高的新数组,这里我设计了一个工厂函数以及计算函数,计算函数核心就是利用上面的公式求图片高度,而工厂函数则是用来输出每一行的图片数组,通过判断计算的高度如果超出阈值,就继续增加这一行的图片(一个隐藏的事实是,该行图片越多高度肯定就会越小),如果高度在我们设置的阈值之内那么就将这些图片"打包"返回,在函数中会拼成一个二维数组,最后拍平就得到我们要的数据:
前面我们已经写完了图片列表的布局,接下来我们还需要点击能单独放大查看图片,并且可以支持图片缩放与移动来观察细节,实现这些操作的关键点在于 CSS3 中的 变换。而实现PC上的点击、移动,H5的手势操作,则离不开DOM事件监听:例如鼠标移动事件对应 ,移动端触摸移动则对应 ,而在本项目中我们不做两套适配,将仅通过指针事件()进行多端统一的事件监听。
这一部分展开来讲篇幅不小,所以我又用 原生JS实现了一遍并把完整的过程和思路都写在了这篇文章中:《 原生JS手写一个优雅的图片预览功能,带你吃透背后原理》即使你使用手机拍照,并不关心照片拍摄时的设备与其它参数,你最起码也应该知道这张照片是在何时拍摄的,而且照片的时间也是我们排序图片的一个重要依据。前面我们用NodeJs读取照片源文件时,尽管通过可以获取文件的创建/修改时间,但这并不能代表实际拍摄时间,此时我们就要通过 Exif (Exchangeable image file format) 来获得照片中记录的数据。
照片如果经过一些美图app的处理,元数据会被抹掉,在PS、LR等专业修图软件中导出成片时,也别忘了勾选保留照片元数据,另外微信传图(即使选择原图)也会丢失元数据,这类软件是出于保护隐私考虑。在网页项目中我们可以直接引入读取照片元数据,不过在本项目中,图片会先经过一个处理阶段,所以我们直接在Node中解析好数据:
安装一下 这个库:
这一步没太多好讲的,利用插件解析出来整理好需要的数据就可以了,只需要注意不要丢失照片文件的原始数据否则读取不到。
在网页中通常我们可以利用HTML5提供的Canvas来获得解码图片的能力,但在NodeJs环境中使用Canvas会比较麻烦,所以这里推荐 images 这个轻量级编解码库,处理图片的速度还是蛮快的,不过我在 MacOX 中安装最新版本会报错,只能锁定这个版本(如果你的网络情况不好导致安装失败,推荐使用 pnpm 安装)
为什么要解码图片呢?如果你经常接触移动端H5开发,应该碰到过图像翻转问题,因为手机照片通常会以原始的拍摄方向展示,可能你在手机上看着没什么问题,但是直接链接到网页上显示就会发现图像翻转了,所以我们需要手动翻转图像到正确位置并重新编码图像。
回到我们的项目中,配合 image-size 可以快速获得照片的方向值,判断如果方向偏移,那么直接使用 images 这个库编码保存,图像会自动翻转到正确方向。
通过 images 重新编码的照片,元数据会被抹除,所以我们也可以配合前面 exif 检查照片如果存在 GPS 对象,就不直接复制照片而是重新编码照片以此消除隐私信息,当然相对的处理速度就会变慢。
照片原始图像并不小,如果直接显示在列表中肯定是不行的,所以我们要在列表中显示缩略图,在实际点击某张图片时再请求原始图。
通过上面的图片编解码库,我们也可以方便地生成缩略图了:
以压缩到目标宽度500,质量70%为例,可以看到压缩率还是不错的:
尽管我们有了缩略图,加载图片列表速度大大提升了,但请求到显示图片一样需要时间,为了避免加载完成前的白屏,我们还可以用颜色来充当占位,保证DOM结构渲染完立即正常显示页面。
我们简单编写一个组件来取代 img 标签显示图片,当图片未加载完成时则显示一个颜色,注意这里的 img 不能用隐藏,应该让它存在DOM当中,否则回调不会触发:
如果随机生成个颜色占位,就显得太不专业了,我们可以用 colorthief 这个库来提取图片的主题色:
参数解释:: 在Node中运行时,这个参数为图像的路径。
: 是一个可选参数,必须是值为1或更大的Integer,默认值为10。这个数字决定了在下一个采样之前跳过多少像素。数值越大,返回值的速度越快。
得到的颜色为数组(代表RGB三原色),我们可以转换成16进制颜色:
实际使用的过程中发现,在NodeJs中不仅处理速度很慢,还容易因为内存不足等问题发生崩溃,打开 colorthief 的包发现,其依赖的是 get-pixels 这个库解码图像,这是一个纯 Javascript 实现的库,性能非常低,所以我这里没有直接使用原图来提取主题色,而是使用了前面我们用 images 解码图像生成的缩略图来作为提取颜色的图片,这样就保证了速度与稳定性。
图片的加载到目前已经很流畅,但由于我们一进来就渲染了全部图片,这样图片太多时体验肯定会变差,作为一个有追求的页面仔,我们当然希望的是用户滑动/滚动到哪里,哪里才会显示图片,也就是常说的懒加载,那么如何实现呢?通常我们会想到利用页面滚动事件监听,上面写列表样式时我们已经可以得到图片的大小和位置,结合与浏览器窗口高度就不难判断图片是否在当前窗口中了,利用这个原理我还可以实现滚动时变换时间的效果:
但是这里图片懒加载我使用另一种方式实现:Intersection Observer,这是一个浏览器原生API,可以用于异步观察目标元素与其祖先元素或顶级文档视窗是否交叉的方法,简单讲就是可以监听一个元素是否出现在视窗当中,就这么简单粗暴,该API其实已经提出很久了,所以不用太担心兼容性问题。
实际效果如下所示:
只有图片太单调,来点音乐吧~进入网易云网页版,登录后打开控制台,点击进入“我的音乐”中,此时找到这个接口,就可以看到你的歌单啦,比如我这里创建了一个叫"PhotoGallery"的歌单,找到它的歌单,等下就把它搬进相册当中。
接下来我们使用 NeteaseCloudMusicApi 这个库,它利用CSRF伪造请求头来调用网易云官方API,通过它我们可以轻松获取到歌单数据以及播放音乐的链接了,虽然它需要部署在服务端,但是没关系,作者提供了 Vercel 部署的配置,仓库里的文件有详细部署说明,这里就不过多赘述了,总之 Vercel 是国外一个部署前端应用的云平台,我们把node项目部署上去就可以直接使用了。
Vercel 网址在国内被墙不能直接访问,不过我们可以通过配置 CNAME解决,如果你有自己的域名可以 fork一下玩玩,没有也没关系,本项目已经配置好了我的域名,不过稳定性不敢保证~为了方便与美观,我们直接使用 APlayer 作为播放器界面,由于不是必要插件,所以我通过JS动态引入来使用,就不通过npm安装了。
这个组件很简单,调用接口,通过 获取歌单详情,然后通过歌单中的歌曲id获取到歌曲播放地址的,不过需要注意,由于我们的宗旨是搭建免费网站,缺少服务端,此方式因为没有登录状态,获取到的歌单曲目只会显示 10 首,但是也够用了(总不能把网易云账号密码写进前端项目里吧)
效果大概就是这样,非常朴实无华:
你在打码的时候又喜欢听些什么歌呢?
虽然只是闲暇之余随手做的小项目,主要是为了方便整理相机拍摄的照片,显示一些参数什么的,可能本身没有太多亮点,但回顾也有不少前端知识与技巧,姑且作为实战分享写下这篇文章,当然如果你喜欢这个项目,也可以快速制作自己的免费在线相册,分享你的美图和摄影佳作吧!
本文地址:http://lianchengexpo.xrbh.cn/quote/12936.html 迅博思语资讯 http://lianchengexpo.xrbh.cn/ , 查看更多