• 主页
  • 归档
  • 分类
  • 照片墙
所有文章 友情链接 关于我

  • 主页
  • 归档
  • 分类
  • 照片墙
  1. 1. 上传图片
  2. 2. 创建自定义页面
    1. 2.1. 瀑布流布局
  3. 3. 滚动加载
    1. 3.1. 动态引入photo-wall.js文件
  4. 4. 效果

照片墙施工记录

2019-05-12 15:17:29
总字数 2.4k
预计阅读时间 10 分钟

依稀记得刚使用hexo的时候, 大部分的照片墙解决方案还是调用Instagram的接口
毕竟这是个很方便上传和管理照片的平台
如今墙一天比一天高, 这方案也基本不灵了
现在博客用上了对象存储作为图片库, 照片墙实现起来也可以有另一套办法了

先整理一下思路, 大概是以下几个步骤

  1. 上传图片到对象存储仓库, 这个可以写个脚本跑一遍就可以了
    但是考虑到页面加载这些图片时候的性能问题
    计划是把100张图作为一组, 每组一个目录, 这个目录当中除了这100张图片, 还有一个json文件, 是该组的文件列表
    为动态加载提供方便
  2. 创建自定义页面, 这个hexo本身就支持, 直接在里面写html代码即可
    但是由于hexo本身对于md文件的渲染策略, 每一行都会加上<br>
    在普通文章里面没什么, 在这个纯html页面就会影响DOM结构, 修改hexo的渲染策略会影响所有页面
    所以要注意这个md文件正文不能有换行
  3. 照片墙页面的布局, 这个准备采用瀑布流的模式, 跟已被关闭的Google plus (深切缅怀🕯) 一样的布局结构
  4. 分页加载以及滚动加载的一些实现

上传图片

根据这次的需要改造一下图片上传的js脚本
跟之前的差不多, 不过这次引入一个images库
这个库有一些对图片进行操作的API, 准备用它来获取到图片的宽高, 也写入到json文件里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
const argv = {
rootPath: 'F:\\WallPaper\\', // 本地图片所在位置
prefix: 'photo-wall',
step: 100
}

const listImages = require('./list_images')
// 当前本地存在的所有图片
const imagesList = listImages(argv.rootPath, argv.prefix)

const setting = require('./auth_info.json'),
fs = require('fs'),
path = require('path'),
nos = require('@xgheaven/nos-node-sdk'),
images = require('images')
const client = new nos.NosClient(setting)

_uploadObject(imagesList)

/**
* 上传文件对象
* @param {Array} filesList 待上传的文件列表
* @param {Number} index 索引值
* @param {Array} group 文件的分组信息
*/
function _uploadObject(filesList, index=0, groups=[]) {
if(index >= filesList.length) {
groups[groups.length-1].end = index
uploadList(filesList, groups)
return
}
if(!groups.length) { // 对于空对象, 放入第一个分组
groups.push({start:index})
}
let img = images(path.resolve(argv.rootPath, filesList[index].name))
filesList[index].width = img.width()
filesList[index].height = img.height()

let objectKey = filesList[index].name.replace(argv.prefix, `${argv.prefix}/${groups.length}`)
let body = fs.createReadStream(path.resolve(argv.rootPath, filesList[index].name))
filesList[index].name = objectKey
if((index + 1) % argv.step === 0) {
// 到达一个分组的末尾
groups[groups.length-1].end = index
uploadList(filesList, groups)
groups.push({start:index+1})
}
client.putObject({objectKey, body}).then(result => {
// eTag是上传后远端校验的md5值, 用于和本地进行比对
let eTag = result.eTag.replace(/"/g,'')
if(filesList[index].md5 === eTag) {
console.log(`${filesList[index].name} 上传成功, md5:${eTag}`)
} else {
console.warn(`${filesList[index].name} 上传出错, md5值不一致`)
console.warn(`===> 本地文件: ${filesList[index].md5}, 接口返回: ${eTag}`)
}
_uploadObject(filesList, ++index, groups)
})
}

/**
* 上传文件列表json
* @param {Array} filesList
* @param {Array} groups
*/
function uploadList(filesList, groups) {
client.putObject({
objectKey: `${argv.prefix}/${groups.length}/list.json`,
body: Buffer.from(JSON.stringify({
start: groups[groups.length-1].start,
end: groups[groups.length-1].end,
files: filesList.slice(groups[groups.length-1].start, groups[groups.length-1].end+1)
}))
}).then(result => {
console.log(result.eTag)
})
}

其中需要对文件名进行替换修改, 改为在该分组内的正确目录
执行完成后, 对象存储仓库的photo-wall目录下就已经有若干个数字命名的子目录了
每个子目录里面都有至多100张图片和一个json文件
比如第一组中json文件的内容为

1
2
3
4
5
{
"start":0,
"end":99,
"files":[{"name":"xxx.png","md5":"xxxx","width":300,"height":200}...]
}

创建自定义页面

在source目录下创建photo_wall目录, 其中创建index.md文件

1
2
3
4
5
6
7
---
title: 照片墙
date: 2019-05-12 15:50:10
pageid: PhotoWall
---

<div id="photo-wall"></div><div id="load-tip">正在加载ԅ( ¯་། ¯ԅ)</div>

这里写个pageid是为了方便在js当中区分自定义页面
从而执行chunk的动态加载, 避免影响其他页面的加载速度

瀑布流布局

得益于浏览器对多列布局的良好实现, css写起来还是非常简单的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#photo-wall {
margin: 0 auto;
column-count: auto;
column-width: 240px;
column-gap: 20px;
// 每一列图片包含层
.item {
margin-bottom: 20px;
// 防止多列布局,分页媒体和多区域上下文中的意外中断
break-inside: avoid;
}
// 图片
.item-img {
width: 100%;
vertical-align: middle;
}
}
#load-tip {
color: $color9;
text-align: center;
display: none;
}

指定列宽, 不指定列数, 根据容器的大小自动适配

虽然指定了列宽, 但是列宽也不是固定的, 这个值相当于是个可允许范围内的最小值 ( 多于1列的情况下 )
比如上面的css当中指定列宽240px, 列间距20px ( 这个值是固定的 )
如果外部容器的宽度600px, 假如排3列, 那么每一列的宽度就是 ( 600 - 20 * 2 ) / 3 ≈ 186.67 < 240
所以无法容纳3列
假如排2列, 那么每一列的宽度是 ( 600 - 20 ) / 2 = 290 > 240
实际就会显示为2列, 每一列的宽度是290px, 列间距20px
换句话说, 就是剩余的宽度会平均分布到各列

滚动加载

有一些第三方库实现了滚动加载, 但是尝试过之后发现无法与现有的整体布局很好地结合
于是决定自己实现一下
photo-wall.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import axios from 'axios'

var groupid = 1, currentIndex = 0, defaultStep = 20, scrollLock = false

// 滚动区域DOM
const scrollDom = document.getElementById('container')
// 作为底部标记的DOM
const markDom = document.getElementById('footer')
// 加载提示文字
const loadTip = document.getElementById('load-tip')

function loadMoreItems(step) {
scrollLock = true //加载过程中锁定滚动加载
loadTip.style.display = 'block'
// 滚动到底部时调用
axios.get(`${themeConfig.pictureCdn}/photo-wall/${groupid}/list.json`).then(res => {
var itemContainer = document.createElement('div')
var imgItems = '', index = currentIndex
while(index<currentIndex+step && index<res.data.files.length) {
let imgHeight = null
if(res.data.files[index].width && res.data.files[index].height) {
let wrapperWidth = photoWallWrapper.getBoundingClientRect().width
// 列宽240px 列间距20px, 计算每列宽度
let columnWidth = (wrapperWidth + 20) / Math.floor((wrapperWidth + 20) / (240 + 20)) - 20
// 图片的实际显示高度
imgHeight = (columnWidth / res.data.files[index].width) * res.data.files[index].height
imgHeight = Math.round(imgHeight * 100) / 100 // 四舍五入保留2位小数
}
imgItems += `<div class="item" ${imgHeight ? 'style="height:' + imgHeight + 'px"' : ''}>
<img class="item-img" src="${themeConfig.pictureCdn}/${res.data.files[index].name}" alt=""/>
</div>`
index++
}
if(index >= res.data.files.length) { // 已到达当前分组列表的末尾
groupid++
let tempIndex = currentIndex
currentIndex = 0
if(index<currentIndex+step) { // 如果加载的数据数量不足步长
// 则需要再加载下一个分组, 下一个分组需要加载的图片数量是剩余的步长
loadMoreItems(tempIndex + step - index)
}
} else {
currentIndex = index
}
itemContainer.classList.add('item-container')
itemContainer.insertAdjacentHTML('beforeend', imgItems)
document.getElementById('photo-wall').appendChild(itemContainer)
setTimeout(()=>{
loadTip.style.display = 'none'
scrollLock = false
}, 2000)
}).catch(res => { // 未加载到文件列表, 代表已经没有更多图片
scrollLock = true
loadTip.textContent = '没有更多图片啦/(ㄒoㄒ)/~~'
})
}

//检测是否具备滚动条加载数据块的条件
function checkScrollSlide(){
var scrollH = scrollDom.scrollTop || document.body.scrollTop || document.documentElement.scrollTop
var clientHeight = document.body.clientHeight || document.documentElement.clientHeight
var footerOffetTop = markDom.offsetTop
return scrollH + clientHeight > footerOffetTop
}

function init() {
var _onscroll = scrollDom.onscroll
var timer = null
scrollDom.onscroll = function () {
// 保留已有的滚动事件回调函数并在新的回调函数中进行调用
typeof _onscroll === 'function' && _onscroll.apply(this, arguments)
if(scrollLock) return
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
if(checkScrollSlide()) {
loadMoreItems(defaultStep)
}
timer = null
}, 200)
}
loadMoreItems(defaultStep)
}
export default { init }

有几点需要注意

  1. 滚动事件需要使用函数防抖方式, 防止滚动事件频繁触发导致的性能问题
  2. 对已存在的滚动事件回调函数要注意保留和调用, 避免直接覆盖
  3. 记录当前分页加载所在的位置, 并在当前分组到达末尾的时候切换到下一个分组
  4. 当不存在下一个分组时, ajax获取下一个分组的json文件会返回404, 要在catch当中处理没有更多图片的交互逻辑
  5. 判断是否滚动到容器底部要添加不同浏览器的兼容
  6. 之前记录下的图片宽高用于指定div在页面中的实际高度, 避免加载过程中频繁打乱整体布局
    ( 因为图片还未加载完成的时候浏览器也不知道这个图片的实际尺寸 )
  7. 主要目标就是不使用jQuery

动态引入photo-wall.js文件

利用webpack的分块动态引入的功能

1
2
3
4
5
6
if(window.themeConfig.pageid === 'PhotoWall') {
// 自定义的照片墙页面
import(/* webpackChunkName: "photo-wall" */ './photo-wall').then(PhotoWall => {
PhotoWall.default.init()
})
}

注释中的webpackChunkName是webpack可以读取的分块打包声明
该引入会被单独打包为一个chunk

效果

照片墙

  • 前端
  • Hexo
  • 前端杂烩

扫一扫,分享到微信

TypeScript初见
博客部署调整记录 
© 2024 夏夜梦星辰
鲁ICP备19028444号
Power By Hexo
  • 所有文章
  • 友情链接
  • 关于我
{{searchItem.query}}
标签: 分类:
  • maven
  • 持续集成
  • JMS
  • 线程
  • JavaScript
  • ECMAScript6
  • 单元测试
  • Promise
  • Web Worker
  • 函数
  • prototype
  • 模块化
  • 正则表达式
  • 数据库
  • MongoDB
  • 索引
  • 集群
  • 全文检索
  • flutter
  • dart
  • git
  • 版本控制
  • linux
  • shell
  • docker
  • nginx
  • jenkins
  • opencv
  • vim
  • react
  • react native
  • 前端
  • css
  • HTML5
  • Hexo
  • sass
  • Three.js
  • TypeScript
  • Vue
  • 组件化
  • base64
  • webpack
  • nodejs
  • gulp
  • TensorFlow
  • 机器学习
  • 算法
  • 动态规划
  • 数据结构
  • Java
  • JavaScript
  • MongoDB
  • flutter
  • Git
  • linux
  • react
  • 前端杂烩
  • 男生女生
  • 算法
  • 十年饮冰,难凉热血
  • †少女癌†
  • 猫与向日葵
  • coderfun
  • JENKINS
  • API管理后台
愿你最终能接纳每一面每一种的自己
独自活着便是团圆