Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

时间:2022-12-05 14:58:51

项目背景

作为一名阿宅,摄影可能是为数不多能让我出门的事情了,以前在广州有很多漫展,基本一两个月必有一场,我也经常会去蹭拍coser,不得不说拍照技术都是在那段时期锻炼出来的。可惜好景不长,这几年疫情反复不断,距离上一次我拿起相机甚至可以追溯到两年前,实在是泪目。既然不能出去拍照,那只能继续宅着敲代码度日了,于是就有了这个在线相册的小项目,用来方便自己放上一些觉得还不错的摄影作品,当然,也可以记录与展示一些生活照片。

前言

这是一个纯前端的项目,不需要开发后端,没有数据库,只需要把照片丢进去,Git提交一下站点就出来了,就很简单。不用买服务器,不搞OSS,总之就是完全免费,全是白嫖的艺术,甚至看完文章不点赞不收藏不评论白嫖我,这篇文章中我会介绍整个项目的开发历程,看完你将收获:

  • 一个 Vue3 + Node 快速生成漂亮的在线相册的项目
  • 实现瀑布流、懒加载、Node解码图片、缩略图生成、元数据读取、提取颜色等技巧
  • 快速集成一个音乐播放器,同步网易云歌单

如何部署

仓库地址:github.com/palxiao/fas…

  1. fork项目,clone到本地,运行 ​​npm run pre​​ 安装依赖
  2. 把你的照片放至 ​​resource​​ 目录中
  3. 修改​​config.json​​​配置(如有必要),运行 ​​npm run start​​,自动完成部署

如何开启 Pages 说明

仓库开启了 Pages 功能才可以在线访问,我们可以使用 Github 或 Gitee 的 Pages 服务。

  1. GithubPages 开启方法:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

  1. Gitee 码云 Pages 开启方法:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

注意:码云开启Pages需要手持身份证照片实名,审核大概要一个工作日时间

个人体验下来 Github 的访问速度比码云更快,当然有被墙的风险,我是绑了自己的域名并加了一层腾讯云免费CDN,应该还是比较稳定的。另外码云不会自动更新网站,要手动进仓库操作,并且还不能自定义域名,居然要收钱。。关键访问速度本就一般。。小了呀。

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

项目运行起来后大概是这个样子:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

网速慢图片多挂着不管就行,提交好了Github会自动部署,部署完毕commit记录旁边会有个绿色的打钩,网站就可以访问了~

点此查看我的在线相册m.palxp.com (手机访问效果最佳)

接下来我将从零开始,讲解我是如何一步步开发完成这个小项目的,Let's Go~

准备工作

开始前,我们需要做点微小的工作,项目结构不复杂,根目录view为前端工程目录,我们先在view目录中创建一个 Vite + Vue3 的项目,即便你没有接触过Vue3,访问ViteVue官网看看文档应该很快就能创建一个基础项目了。

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

好了,现在把照片放在resources目录,后续我们只要在电脑上对这个目录中图片进行管理,重新运行即可更新在线相册,这也就不必开发后台管理照片了。

首先创建一个​​index.js​​文件使用NodeJs来遍历resources,我们要获得路径名称、图片大小等有用信息(使用 image-size 这个库可以快速获取图片大小),然后输出成JSON文件保存,最后将图片复制到目录​​view/public/​​中:

const fs = require('fs')
const path = require('path')
const sizeOf = require('image-size')

const basePath = path.resolve('resources')
const jsonPath = path.resolve('view/src/assets/data/datalist.json')
const picsData = []

fs.readdir(basePath, async function (err, files) {
//遍历读取到的文件列表
for (let i = 0; i < files.length; i++) {
const filename = files[i]
const filedir = path.join(basePath, filename)
//根据文件路径获取文件信息,返回一个fs.Stats对象
const stats = await fs.statSync(filedir)
if (stats.isFile()) {
let dimensions = { url: filename, ...sizeOf(filedir) }
if (dimensions.width) {
cp(filedir, path.resolve(`view/public/${filename}`)) // 复制图片
picsData.push({ ...dimensions })
}
}
}
// 解析完毕,生成json
fs.writeFileSync(jsonPath, JSON.stringify(picsData))
})

// 复制文件
function cp(from, to) {
fs.writeFileSync(to, fs.readFileSync(from))
}

运行​​node index.js​​,这样我们就可以开始编写前端项目了,把JSON文件引入即是图片列表数组,为了摆放好这些图片,我们先来写写图片列表的样式布局。

瀑布流排版

瀑布流是一种经典的图片排列模式,其特点是等宽不等高,在保持原图比例下显示布局。

目前并没有纯CSS可以完美实现的瀑布流方法,常见的多列布局(multi-columns)实际效果非常差强人意,它会得到如下的一个竖排布局效果,在实际应用中会感觉图片是乱序排列的。

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

其实用JS实现瀑布流也并不难,我们使用绝对定位布局,由于图片的宽度是平均分布的,只需要计算出图片的高度及​​left​​​、​​top​​定位对应设置到图片元素上即可:

<template>
<div >
<div :style="{ position: 'absolute', width: `${img.w}px`, height: `${img.h}px`, left: `${img.left}px`, top: `${img.top}px` }" v-for="(img, i) in list" :index="i" :key="'img' + i">
<img ............ />
</div>
</div>
</template>

我们用变量​​columnNums​​​表示有多少列,​​gap​​表示图片间隔,容器总宽度可以由当前的DOM往父级查询​​parentNode.offsetWidth​​​来获取,那么图片在布局中的宽高以及​​left​​​值就可以计算出来了,而高度则用一个数组​​columnHeights​​​来储存,有多少列就往里存多少个元素,随着图片列表的循环一直累加取出计算就可以得到每张图片的​​top​​定位了,简单几行代码搞定:

let columnNums = 2 // 有多少列
const gap = 8 // 图片之间的间隔
const columnHeights = [] // 列的高度

function waterfall(data) { // data为图片数组
const columnHeights: any = [] // 列的高度
let { offsetWidth: pW } = document.getElementById('list').parentNode.offsetWidth
pW -= gap * (columnNums - 1) // 总体宽度数值等于减去间隔
const newList = JSON.parse(JSON.stringify(data))
for (let i = 0; i < newList.length; i++) {
let index = i % columnNums
const item = newList[i]
item.w = pW / columnNums // 图片宽度
item.h = item.height * (pW / columnNums / item.width) // 图片高度
item.left = index * (pW / columnNums + gap) // 定位
item.top = columnHeights[index] + gap || 0 // 定位
columnHeights[index] = isNaN(columnHeights[index]) ? item.h : item.h + columnHeights[index] + gap // 记录列高度
}
return newList
}

两列效果:

改成三列:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

这时候虽然瀑布流的样式已经出来了,但拉到列表底部就会发现瘸腿了:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

经过​​1.024​​秒的思索,我马上发现了问题所在,上面的代码仅仅只是把图片按左右的顺序依次往下排列,而每张图片高度是不一样的,这就导致出现尾部空白的现象,解决的办法也很简单,每次找出最短的那一列来插入图片即可,我们已经将高度都存在了​​columnHeights​​​这个数组中,通过往​​Math.min()​​传入解构数组得到最小值,再用​​indexOf​​得到下标,就可以知道下一张图片该插入哪一列了,修改上面方法中的最后一行代码:

function waterfall(data) { // data为图片数组
// ...........
// columnHeights[index] = isNaN(columnHeights[index]) ? item.h : item.h + columnHeights[index] + gap
// TODO: 上面这行代码改为找出最短列计算高度
if (isNaN(columnHeights[index])) {
columnHeights[index] = item.h
} else {
index = columnHeights.indexOf(Math.min(...columnHeights))
item.left = index * (pW / columnNums + gap)
item.top = columnHeights[index] + gap || 0
columnHeights[index] = item.h + columnHeights[index] + gap
}
}

以上就是实现瀑布流排版的全部核心代码,可以根据实际情况进行扩展,比如通过​​window.onresize​​监听窗口宽度变化动态改变列数量重新排列等。

书架流排版

这个图片排版样式比较少见,但同样是等比例显示图片,区别于瀑布流的是图片不等宽,但同一行图片等高,所以我把它命名为书架流,看着是不是很像整齐排列在书架上的书本:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

虽然看起来似乎是等高不等宽,但实际上每行高度并不都是一样的,因此我们需要一个阈值来决定每行高度可以被允许的上限,与瀑布流一样的是,列表整体宽度是已知的,所以核心是计算每行图片的高度,我们先来看看如何实现这个算法。

抽象问题用简单的数学问题描述往往更容易解决。假设某行存在2张图片,已知的实际宽高分别为​​w1​​​、​​h1​​​和​​w2​​​、​​h2​​​,而在列表中的相对宽高我们则设为​​w1'​​​、​​h1'​​​和​​w2'​​​、​​h2'​​​,接着设列表总体宽高为​​W​​​和​​H​​​,已知的​​W​​​为列表父级div的宽度,我们的目的就是求这个​​H​​的值。

此时由于在同一行中图片等高,于是有:​​h1'​​​=​​h2'​​​=​​H​

又因为图片的比例不变,于是有:​​w1'​​​/​​h1'​​​ = ​​w1​​​/​​h1​​,代入上面的式子可得:

​w1'​​​/​​H​​​ = ​​w1​​​/​​h1​​​(同理得到另一个式子:​​w2'​​​/​​H​​​ = ​​w2​​​/​​h2​​)

所以上面推导的两个式子可以得出图片在行内的宽度分别为:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

而总体宽度为图片宽度相加:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

代入可得到:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

到这里我们已经可以轻松推导出计算高度​​H​​的方法了:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

上面推导过程我是在纸上完成的,回到代码中,我们可以使用递归来操作图片数组,得到一组计算好宽高的新数组,这里我设计了一个工厂函数​​factory​​​以及计算函数​​calculate​​,计算函数核心就是利用上面的公式求图片高度,而工厂函数则是用来输出每一行的图片数组,通过判断计算的高度如果超出阈值,就继续增加这一行的图片(一个隐藏的事实是,该行图片越多高度肯定就会越小),如果高度在我们设置的阈值之内那么就将这些图片"打包"返回,在​​handleList​​函数中会拼成一个二维数组,最后拍平就得到我们要的数据:

const gap = 8 // 图片之间的间隔
let limitWidth = document.getElementById('list').parentNode.offsetWidth // 宽度限制,列表父级div宽度
const list = JSON.parse(JSON.stringify(data)) // data为原始图片数组
const newList = await createNewArr(list)

async function createNewArr(list) {
const standardHeight = 180 // 高度阈值
const neatArr = [] // 整理后的数组
function factory(cutArr) {
return new Promise((resolve) => {
const lineup = list.shift()
if (!lineup) {
resolve({ height: calculate(cutArr), list: cutArr })
return
}
cutArr.push(lineup)
const finalHeight = calculate(cutArr)
if (finalHeight > standardHeight) { // 如果计算超出阈值,就继续加入图片
resolve(factory(cutArr))
} else {
resolve({ height: finalHeight, list: cutArr })
}
})
}
function calculate(cutArr) {
let cumulate = 0
for (const iterator of cutArr) {
cumulate += iterator.width / iterator.height
}
return (limitWidth - gap * (cutArr.length - 1)) / cumulate // 实际宽度需要减去图片间隔
}
async function handleList() {
const { list: newList, height } = await factory([list.shift()])
neatArr.push( newList.map((x) => { x.w = (x.width / x.height) * height; x.h = height; return x }))
if (list.length > 0) {
await handleList()
}
}
await handleList()

return neatArr.flat()
}

图片预览与查看

前面我们已经写完了图片列表的布局,接下来我们还需要点击能单独放大查看图片,并且可以支持图片缩放移动来观察细节,实现这些操作的关键点在于 CSS3 中的 ​​transform​​ 变换。而实现PC上的点击、移动,H5的手势操作,则离不开DOM事件监听:例如鼠标移动事件对应 ​​mousemove​​​,移动端触摸移动则对应 ​​touchmove​​,而在本项目中我们不做两套适配,将仅通过指针事件(​​pointEvent​​)进行多端统一的事件监听。

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

这一部分展开来讲篇幅不小,所以我又用原生JS实现了一遍并把完整的过程和思路都写在了这篇文章中:《原生JS手写一个优雅的图片预览功能,带你吃透背后原理》

获取照片元数据

即使你使用手机拍照,并不关心照片拍摄时的设备与其它参数,你最起码也应该知道这张照片是在何时拍摄的,而且照片的时间也是我们排序图片的一个重要依据。前面我们用NodeJs读取照片源文件时,尽管通过​​fs.stat​​可以获取文件的创建/修改时间,但这并不能代表实际拍摄时间,此时我们就要通过 Exif (Exchangeable image file format) 来获得照片中记录的数据。

照片如果经过一些美图app的处理,元数据会被抹掉,在PS、LR等专业修图软件中导出成片时,也别忘了勾选保留照片元数据,另外微信传图(即使选择原图)也会丢失元数据,这类软件是出于保护隐私考虑。

在网页项目中我们可以直接引入​​exif.js​​读取照片元数据,不过在本项目中,图片会先经过一个处理阶段,所以我们直接在Node中解析好数据:

安装一下 ​​exif​​​ 这个库:​​npm install exif​

const ExifImage = require('exif').ExifImage
new ExifImage({ image: filedir }, async function (error, exifData) {
if (!error) {
const { ImageWidth: width, ImageHeight: height, ModifyDate } = exifData.image // 获取到图片一些数据
let datetime = exifData.exif.DateTimeOriginal || ModifyDate // 元数据
// TODO: 解析出来的格式不标准,转化成我们可以使用的:
datetime && (result.datetime = datetime.split(' ')[0].replace(/:/g, '-') + ' ' + datetime.split(' ')[1].slice(0, 8))
if (JSON.stringify(exifData.gps) === '{}') {
// 定位属于隐私信息
}
}
})

这一步没太多好讲的,利用插件解析出来整理好需要的数据就可以了,只需要注意不要丢失照片文件的原始数据否则读取不到。

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

Node解码图片

在网页中通常我们可以利用HTML5提供的Canvas来获得解码图片的能力,但在NodeJs环境中使用Canvas会比较麻烦,所以这里推荐 ​​images​​ 这个轻量级编解码库,处理图片的速度还是蛮快的,不过我在 MacOX 中安装最新版本会报错,只能锁定​​3.2.3​​这个版本(如果你的网络情况不好导致安装失败,推荐使用 pnpm 安装)

为什么要解码图片呢?如果你经常接触移动端H5开发,应该碰到过图像翻转问题,因为手机照片通常会以原始的拍摄方向展示,可能你在手机上看着没什么问题,但是直接链接到网页上显示就会发现图像翻转了,所以我们需要手动翻转图像到正确位置并重新编码图像。

回到我们的项目中,配合 image-size 可以快速获得照片的方向值,判断如果方向偏移,那么直接使用 images 这个库编码保存,图像会自动翻转到正确方向。

const sizeOf = require('image-size')
const images = require('images')

const filedir = '' // <- 图像地址
const dimensions = sizeOf(filedir)
if ([6, 8, 3].includes(dimensions.orientation)) {
// TODO:通过解码写入来复制图片,判断方向是否正确。
images(filedir).save(.......) // 写入到新的地址
}

通过 images 重新编码的照片,元数据会被抹除,所以我们也可以配合前面 exif 检查照片如果存在 GPS 对象,就不直接复制照片而是重新编码照片以此消除隐私信息,当然相对的处理速度就会变慢。

Node生成缩略图

照片原始图像并不小,如果直接显示在列表中肯定是不行的,所以我们要在列表中显示缩略图,在实际点击某张图片时再请求原始图。

通过上面的图片编解码库,我们也可以方便地生成缩略图了:

// filedir: 图像地址,thumbSize: 压缩后图像宽度,quality: 压缩质量
images(filedir).size(thumbSize).save(.....), { quality: 70 })

以压缩到目标宽度500,质量70%为例,可以看到压缩率还是不错的:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

生成预载占位颜色

尽管我们有了缩略图,加载图片列表速度大大提升了,但请求到显示图片一样需要时间,为了避免加载完成前的白屏,我们还可以用颜色来充当占位,保证DOM结构渲染完立即正常显示页面。

我们简单编写一个组件来取代 img 标签显示图片,当图片未加载完成时则显示一个颜色,注意这里的 img 不能用​​if​​隐藏,应该让它存在DOM当中,否则​​onLoad​​回调不会触发:

<template>
<div class="img">
<img v-show="!loading" :src="src" @load="loadDone" />
<div v-if="loading" class="color" :style="{ background: data.color }" />
</div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
props: {
src: {},
data: {},
},
setup(props) {
const loading = ref(true)
const loadDone = () => {
loading.value = false
}
watch(() => props.src, () => {
loading.value = true
})
return { loading, loadDone }
},
})
</script>

<style scoped>
.img {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
}
.img > img {
display: block;
width: 100%;
height: 100%;
}
.color {
width: 100%;
height: 100%;
border-radius: 4px;
animation: breathe 600ms ease-out infinite alternate;
}
/* 呼吸效果 */
@keyframes breathe {
0% { opacity: 0.8 }
100% { opacity: 1 }
}
</style>

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

如果随机生成个颜色占位,就显得太不专业了,我们可以用 colorthief 这个库来提取图片的主题色:

const ColorThief = require('colorthief')

ColorThief.getColor(image, quality).then((color) => {
rgbToHex(color) // color 为图片主颜色,格式为三原色数组
}).catch((err) => {})

参数解释:

​image​​: 在Node中运行时,这个参数为图像的路径。

​quality​​: 是一个可选参数,必须是值为1或更大的Integer,默认值为10。这个数字决定了在下一个采样之前跳过多少像素。数值越大,返回值的速度越快。

得到的颜色为数组(代表RGB三原色),我们可以转换成16进制颜色:

const rgbToHex = (rgb) => '#' + rgb.map((x) => {
const hex = x.toString(16)
return hex.length === 1 ? '0' + hex : hex
}).join('')

实际使用的过程中发现,在NodeJs中不仅处理速度很慢,还容易因为内存不足等问题发生崩溃,打开 colorthief 的包发现,其依赖的是 get-pixels 这个库解码图像,这是一个纯 JavaScript 实现的库,性能非常低,所以我这里没有直接使用原图来提取主题色,而是使用了前面我们用 images 解码图像生成的缩略图来作为提取颜色的图片,这样就保证了速度与稳定性。

懒加载

图片的加载到目前已经很流畅,但由于我们一进来就渲染了全部图片,这样图片太多时体验肯定会变差,作为一个有追求的页面仔,我们当然希望的是用户滑动/滚动到哪里,哪里才会显示图片,也就是常说的懒加载,那么如何实现呢?通常我们会想到利用页面滚动事件监听,上面写列表样式时我们已经可以得到图片的大小和位置,结合​​scrollTop​​与浏览器窗口高度就不难判断图片是否在当前窗口中了,利用这个原理我还可以实现滚动时变换时间的效果:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

但是这里图片懒加载我使用另一种方式实现:Intersection Observer,这是一个浏览器原生API,可以用于异步观察目标元素与其祖先元素或*文档视窗是否交叉的方法,简单讲就是可以监听一个元素是否出现在视窗当中,就这么简单粗暴,该API其实已经提出很久了,所以不用太担心兼容性问题。

onMounted(async () => {
await nextTick()
observer()
})

function observer() {
const observer = new IntersectionObserver((entries) => {
entries.forEach((item) => {
if (item.isIntersecting) {
// TODO: 换上真实的图片链接
observer.unobserve(item.target) // 停止监听该节点
}
})
}) //不传options参数,默认根元素为浏览器视口
document.querySelectorAll('.img-box').forEach((div) => observer.observe(div)) // 遍历监听所有图片DOM节点
}

实际效果如下所示:

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

来点音乐

只有图片太单调,来点音乐吧~进入​​网易云网页版​​,登录后打开控制台,点击进入“我的音乐”中,此时找到​​playlist​​​这个接口,就可以看到你的歌单啦,比如我这里创建了一个叫"PhotoGallery"的歌单,找到它的歌单​​id​​,等下就把它搬进相册当中。

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

接下来我们使用 NeteaseCloudMusicApi 这个库,它利用CSRF伪造请求头来调用网易云官方API,通过它我们可以轻松获取到歌单数据以及播放音乐的链接了,虽然它需要部署在服务端,但是没关系,作者提供了 Vercel 部署的配置,仓库里的​​README.MD​​文件有详细部署说明,这里就不过多赘述了,总之 Vercel 是国外一个部署前端应用的云平台,我们把node项目部署上去就可以直接使用了。

// api.js
import fetch from '@/utils/axios'
// 文档地址:https://binaryify.github.io/NeteaseCloudMusicApi/#/
// 获取歌曲播放链接
export const getUrl = (params: Type.Object = {}) => fetch(MUSIC_URL + '/song/url', params, 'get')
// 通过 id 获取歌单详情
export const getList = (params: Type.Object = {}) => fetch(MUSIC_URL + '/playlist/detail?id=5183094117', params, 'get')

Vercel 网址在国内被墙不能直接访问,不过我们可以通过配置CNAME解决,如果你有自己的域名可以fork一下玩玩,没有也没关系,本项目已经配置好了我的域名,不过稳定性不敢保证~

为了方便与美观,我们直接使用 APlayer 作为播放器界面,由于不是必要插件,所以我通过JS动态引入来使用,就不通过npm安装了。

// deferLoader.js 异步加载脚本方法
export default (type, url) => {
return new Promise((resolve) => {
const link_element = document.createElement(type)
if (type === 'script') {
link_element.setAttribute('src', url)
} else if (type === 'link') {
link_element.setAttribute('rel', 'stylesheet')
link_element.setAttribute('href', url)
}
document.head.appendChild(link_element)
link_element.onload = function () {
resolve()
}
})
}

这个组件很简单,调用接口,通过 ​​id​​​ 获取歌单详情,然后通过歌单中的歌曲id获取到歌曲播放地址的​​url​​,不过需要注意,由于我们的宗旨是搭建免费网站,缺少服务端,此方式因为没有登录状态,获取到的歌单曲目只会显示 10 首,但是也够用了(总不能把网易云账号密码写进前端项目里吧)

<template>
<div ></div>
</template>

<script>
import { defineComponent, onMounted, nextTick } from 'vue'
import * as api from './api'
import loader from '@/utils/widgets/deferLoader'

export default defineComponent({
setup() {
onMounted(async () => {
const ids = []
const listObj = {}
const { data: resList } = await api.getList()

for (const x of resList.playlist.tracks) {
ids.push(x.id)
listObj[x.id] = { name: x.name, artist: x.ar[0] ? x.ar[0].name : '', cover: x.al.picUrl }
}
let { data: audio } = await api.getUrl({ id: ids + '', realIP: '116.25.146.177' }) // ip是随便填的,不填会无法访问接口
audio = audio.data.map((x: any) => {
return Object.assign({ url: x.url }, listObj[x.id])
})
// 如果接口请求成功,下面开始启动播放器
await load() // 下载插件
await nextTick()
const APlayer = window.APlayer
new APlayer({
container: document.getElementById('aplayer'),
fixed: true, // 播放器会吸附在底部,没有兼容iphone黑边,所以下面补了临时处理
autoplay: true,
audio,
})
})
async function load() {
await loader('script', 'https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js')
await loader('link', 'https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css')
}
},
})
</script>

<style>
.aplayer.aplayer-fixed .aplayer-body {
bottom: calc(constant(safe-area-inset-bottom));
bottom: calc(env(safe-area-inset-bottom));
}
</style>

效果大概就是这样,非常朴实无华

Vue3+Node写个免费在线图库生成器,只需三步将你的手机相册搬到线上

你在打码的时候又喜欢听些什么歌呢?

结尾

虽然只是闲暇之余随手做的小项目,主要是为了方便整理相机拍摄的照片,显示一些参数什么的,可能本身没有太多亮点,但回顾也有不少前端知识与技巧,姑且作为实战分享写下这篇文章,当然如果你喜欢这个项目,也可以快速制作自己的免费在线相册,分享你的美图和摄影佳作吧!

项目地址:github.com/palxiao/fas…

我的在线相册:m.palxp.com (手机访问效果最佳)

以上就是文章的全部内容,感谢看到这里,希望对你有所帮助或启发!创作不易,如果觉得文章写得不错,可以点赞收藏支持一下,也欢迎关注,我会更新更多实用的前端知识与技巧。我是茶无味de一天(公众号: 品味前端),期待与你共同成长~