前言
拍的照片多了,设计一个相册功能便于更直观地展示拍的各种好看的照片😇!
参考资料:
正文
子链接的创建
在 hexo 项目的 source
文件中,新建一个 galleries
文件夹,再在里面放上 index.md
(可以使用命令行),
渲染博客的时候就会渲染 /galleries
这个网址,如 相册-Zi-Zi’s Journey 。
同样地,如果在 galleries
里放上其它文件夹,文件夹里再放上 index.md
,渲染博客的时候就会再渲染下一级的网址,如:…/…/…/…/galleries/研究生 。
继续套娃,套到相册(layout 为 gallery
)为止:
相册的入口
设置好了子链接就要设置好入口,一般在主题的 _config.yml
中配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 menus_title: home: 主页 archives: 统计 categories: 类别 tags: 标签 galleries: 相册 links: 链接 about: 关于 menus: home: / archives: /archives categories: /categories tags: /tags galleries: /galleries links: /links about: /about
渲染博客,导航栏就多了一项 相册
,且点进去能够正确转入 /galleries
:
其它有关页面的参数也设置一下:
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 headers: home: { message: "居然被你找到了这里!" , icon: "/images/logo.png" , cover: "#f5f9fd" } archives: { message: "居然用了 year 年一共才写了 number 篇文章!" , icon: "/images/archives.svg" , cover: "#f5f9fd" } categories: { message: "好像也没分几类" , icon: "/images/categories.svg" , cover: "#f5f9fd" } tags: { message: "这里是一些展示的标签" , icon: "/images/tags.svg" , cover: "#f5f9fd" } galleries: { message: "有趣的相册~(施工中)" , icon: "/images/galleries.svg" , cover: "#f5f9fd" } links: { message: "给我读过的学校递一杯卡布奇诺~" , icon: "/images/links.svg" , cover: "#f5f9fd" }
手撸一个风格相近的图标:
创建相册页面的布局
编辑创建的 index.md
,头部信息是由 yaml 语法组成的:
1 2 3 4 title: 相册 date: 2023-12-29 09:46:29 type: galleries layout: galleries
以自己所用主题为例,这表示这个网页页面布局将使用 galleries
渲染。
我设计了两种页面:
galleries
用于展示相册
gallery
用于展示相册里的图片
在主题对应的文件夹里创建 galleries.ejs
和 gallery.ejs
,页面就会按照对应的 .ejs
文件渲染:
galleries 设计
给使用 layout
为 galleries
的 index.md
设计变量,这些变量在galleries.ejs
中会以 page.XXX
的形式读取:
1 2 3 4 5 6 7 8 9 10 title: 相册 date: 2023-12-29 09:46:29 type: galleries layout: galleries layout_style: block comments: false galleries: - {title: "研究生" , description: "Tell me 他乡的困难会不会比它鼓山高?" , cover: "/images/gallery_covers/研究生.jpg" } - {title: "本科" , description: "闽江江水都流向,流向长乐的海……" , cover: "/images/gallery_covers/本科.jpg" } - {title: "小时候" , description: "我让过去的自己只留在,安静的白马河。" , cover: "/images/gallery_covers/小时候.jpg" }
title
标题
date
时间,但在这里我并没用到
type
页面属性
layout
布局属性
layout_style
布局风格,我这里设计了两种风格 block
和 card
galleries
相册列表,列表里的内容必须符合子文件夹的属性(我没有想到如何自动遍历子文件夹并获取相关参数的办法,只能这手动档了。)
title
相册标题
description
相册描述
cover
相册封面
cover_style
我这里没设置,可以控制封面图片的 style
comments
是否打开评论功能
galleries.ejs
galleries.ejs
的内容参考如下:(修改 galleries.ejs
的内容要重新生成博客才可以重新渲染,如果调试不方便,可以将代码以 <%- partial('_partial/XXX' %>
跳转到其他地方以便于调试)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <%- partial('_partial/header' ,{name: 'galleries' }) %><div class="galleries"> <%- partial('_widget/header_body' ,{message: page.headers ? page.headers : theme.headers.galleries.message, icon: theme.headers.galleries.icon, cover: theme.headers.galleries.cover}) %> <div class="main"> <div class="post-<%= page.layout_style ? page.layout_style : 'block' %>" id="content"> <% page.galleries.forEach(function(item) { %> <div class="post-<%= page.layout_style ? page.layout_style : 'block' %>-content"> <a class="img-container" href="<%= item.title %>"> <div class="photo-frames"> <img style="<%- item.cover_style || '' %>" src="<%= item.cover ? item.cover : theme.default_cover %>" alt="Cover"> </div> <p class="title"><%= item.title %></p> </a> <div class="description-container"><p><%= item.description %></p></div> </div> <% }); %> <div id="gitalk-container"></div> </div> </div> </div>
就是将读取到信息展示在页面上的逻辑。
galleries.less
创建并编写对应的 galleries.less
如下(记得在一个地方引用):
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 .galleries { .main { display : flex; flex-grow : 1 ; flex-basis : auto; flex-direction : column; margin-top : -64px ; .post-block { padding : 0 calc ((100% - 1160px )/2 ); margin-bottom : 50px ; & -content { margin : 20px 100px 60px 100px ; text-decoration : none; height : 240px ; justify-content : center; display : grid; grid-template-columns : repeat (auto-fit, minmax (200px , 1 fr)); grid-gap : 10px ; position : relative; top : 0px ; transition : all .5s ease-in-out; -moz-transition : all .5s ; -webkit-transition : all .5s ; -o-transition : all .5s ; & :hover { top : -15px ; } .img-container { justify-content : center; display : flex; transform : rotate (-5deg ); transition : transform ease-in-out 0.5s ; & :hover { transform : rotate (-10deg ); } .photo-frames { width : 200px ; border : 10px solid #FFF ; border-radius : 5px ; background : #FFF ; box-shadow : 0 20px 40px 0 rgba (50 ,50 ,50 ,0.2 ); img { border-radius : 2px ; margin-top : 10px ; width : 100% ; height : 75% ; object-fit : cover; } } } .title { bottom : 15px ; position : absolute; font-weight : bold; text-decoration : none; color : @textColorTheme ; font-size : 22px ; font-weight : 500 ; } .description-container { margin-top : 80px ; p { text-indent : 2em ; font-size : 20px ; position : absolute; } } } @media screen and (max-width :660px ) { & -content { margin : 20px ; padding : 20px ; .title { bottom : 5% ; } .description-container { p { font-size : 18px ; } } } } @media screen and (max-width :489px ) { & -content { height : 320px ; margin-bottom : 40px ; .img-container { .photo-frames { width : 60vw ; } } .description-container { margin-top : 40px ; } } } } .post-card { display : flex; max-width : 100% ; padding : 0 calc ((100% - 1200px )/2 ) 40px ; flex-wrap : wrap; justify-content : center; align-items : stretch; margin-top : -64px ; & -content { margin : 20px 40px 80px 40px ; text-decoration : none; height : 400px ; justify-content : center; display : grid; grid-template-columns : repeat (auto-fit, minmax (200px , 1 fr)); grid-gap : 10px ; position : relative; top : 0px ; transition : all .5s ease-in-out; -moz-transition : all .5s ; -webkit-transition : all .5s ; -o-transition : all .5s ; & :hover { top : -15px ; } .img-container { justify-content : center; display : flex; transform : rotate (-5deg ); transition : transform ease-in-out 0.5s ; & :hover { transform : rotate (-10deg ); } .photo-frames { width : 220px ; border : 10px solid #FFF ; border-radius : 5px ; background : #FFF ; box-shadow : 0 20px 40px 0 rgba (50 ,50 ,50 ,0.2 ); img { border-radius : 2px ; margin-top : 10px ; width : 100% ; height : 75% ; object-fit : cover; } } } .title { bottom : 20px ; position : absolute; font-weight : bold; text-decoration : none; color : @textColorTheme ; font-size : 22px ; font-weight : 500 ; } .description-container { margin-top : 40px ; p { text-indent : 2em ; font-size : 20px ; position : absolute; } } } @media screen and (max-width :640px ) { & -content { margin : 80px 20px ; padding : 20px ; height : 400px ; .title { bottom : 5% ; } .img-container { .photo-frames { width : 60vw ; } } .description-container { p { font-size : 18px ; } } } } } } }
演示
大功告成,对应的 block
风格页面:相册-Zi-Zi’s Journey
\研究生\index.md
的布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 title: 研究生 date: 2023-12-29 14:47:00 type: galleries layout: galleries layout_style: card headers: 大河之北 galleries: - {title: "福州" , description: "WAIYA! 鼓山脚 南门兜 我如鱼得水" , cover: "/images/gallery_covers/研究生/福州.jpg" } - {title: "保定-春夏" , description: "保定没有爱情,只有他蜡笔还不完的饥荒。" , cover: "/images/gallery_covers/研究生/保定-春夏.jpg" } - {title: "保定-秋冬" , description: "雪花飘飘,北风萧萧。" , cover: "/images/gallery_covers/研究生/保定-秋冬.jpg" } - {title: "石家庄" , description: "直到大厦崩塌" , cover: "/images/gallery_covers/研究生/石家庄.jpg" } - {title: "厦门" , description: "再鼓楼润湖里搞涢涢!" , cover: "/images/gallery_covers/研究生/厦门.jpg" } - {title: "武汉" , description: "这辈子又可以见到小迷糊了!" , cover: "/images/gallery_covers/研究生/武汉.jpg" } - {title: "雄安" , description: "千年大计,国家大事。" , cover: "/images/gallery_covers/研究生/雄安.jpg" } - {title: "天津" , description: "天天乐道,津津有味。" , cover: "/images/gallery_covers/研究生/天津.jpg" } - {title: "正定" , description: "太能走了凡哥!" , cover: "/images/gallery_covers/研究生/正定.jpg" }
对应的 card
风格页面:
gallery 设计
同理,给使用 layout
为 gallery
的 index.md
设计变量,这些变量在gallery.ejs
中会以 page.XXX
的形式读取:
1 2 3 4 5 6 7 8 9 10 title: 保定-秋冬 date: 2023-12-29 14:47:00 type: gallery layout: gallery description: 你们南方人的四季是不完整的。——阿杰 imgs: - {title: "积雪的人行道" , src: ../../../XXX.jpg } - ... - {title: "和美保定" , src: ../../../XXX.jpg }comments: true
title
标题
date
时间,但在这里我并没用到
type
页面属性
layout
布局属性
description
相册描述
imgs
定义的一个图片类(暂且先设计一个变量)
title
图片的描述
src
对应的 src 地址,可以是绝对地址也可以是相对地址
comments
是否打开评论功能
逐个输入图像链接是费劲的,可以考虑借助其他工具批量生成这样的信息。
批量生成这样的信息的代码:
1 2 3 4 import osfor file in os.listdir(r'D:\Study\GzBlog-Github\source\_posts\Diary-浙了(二)' ): print (' ' + r'- {title: "XXX", src: /2024/02/26/Diary-浙了(二)/' + file + '}' )
gallery.ejs
在 gallery.ejs
中调用这些变量:
因为这个博客使用了 fancybox 便于轮播图片,所以将 <img>
放在 <a class="fancybox">
下。
相册的布局是瀑布流布局,自己写可太费劲了,引用了 Masonry - imagesLoaded progress (codepen.io) 中的瀑布流插件。
保证引用的 masonry.pkgd.js
和 imagesloaded.pkgd.js
版本一致,不然可能会出现奇奇怪怪的问题。
设计了一个 description-container
用于存放图片的描述。
<script>var lazyLoad = <%= theme.lazyload %></script><%- js('js/gallery.js') %>
貌似是个可行的 EJS 向 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 <%- partial('_partial/header',{name:'galleries'}) %> <%- partial('_widget/header_body',{message: page.description ? page.description : theme.headers.galleries.message, icon:theme.headers.galleries.icon, cover: theme.headers.galleries.cover}) %> <%- js('js/masonry.pkgd.js') %> <%- js('js/imagesloaded.pkgd.js') %> <style> *{ box-sizing: border-box; } </style> <div class="gallery-content"> <div class="grid"> <div class="grid-sizer"></div> <% if (page.imgs && page.imgs.length > 0) { %> <% page.imgs.forEach(function(item) { %> <div class="grid-item"> <a href="<%- item.src %>" title="<%- item.title %>" data-src="<%- item.src %>" class="fancybox" data-fancybox="fancybox-gallery-img" rel="article"> <img src="<%- item.src %>" alt="<%- item.title %>" /> </a> </div> <% }) %> <% } %> </div> <div style="width: 100%; height: 20px;"></div> <div class="description-container"><span></span></div> <div style="width: 100%; height: 20px;"></div> <div id="gitalk-container"></div> </div> <script>var lazyLoad = <%= theme.lazyload %></script> <%- js('js/gallery.js') %>
gallery.less
同理,创建并编写对应的 galleriy.less
如下:
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 .gallery-content { width : 100% ; padding : 2px 0 ; max-width : 1200px ; margin : -64px auto auto auto; border-radius : 10px ; background : #FFF ; .grid :after { content : '' ; display : block; clear : both; } .grid-sizer , .grid-item { width : 33% ; } .grid-item { float : left; padding : 10px ; } .grid-item img { display : block; max-width : 100% ; border-radius : 10px ; } .fancybox :hover { z-index : 2 ; transform : scale (1.1 ); } .description-container { z-index : 2 ; position : sticky; width : 100% ; left : 0 ; right : 0 ; height : 40px ; bottom : 0 ; background : linear-gradient (to right, rgba (0 , 0 , 0 , 0 ), rgba (255 , 255 , 255 , 0.9 ), rgba (255 , 255 , 255 , 0.9 ), rgba (0 , 0 , 0 , 0 )); text-align : center; flex-direction : column; align-items : center; span { font-size : 18px ; color : #12183A ; text-shadow : 0 0 10px rgba (128 , 128 , 128 , 0.8 ); position : absolute; bottom : 50% ; left : 50% ; transform : translate (-50% , 50% ); } } @media screen and (max-width : 660px ) { .grid-sizer , .grid-item { width : 100% ; } } }
gallery.js
function initGallery()
因为设置了懒加载,在图片载入完毕的时候需要执行 $grid.masonry();
以更新布局。
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 function initGallery ( ) { var $grid = $('.grid' ).masonry ({ itemSelector : '.grid-item' , percentPosition : true , columnWidth : '.grid-sizer' }); if (lazyLoad) { window .imageLazyLoadSetting = { onImageLoaded : function ( ) { $grid.masonry (); } }; } else { $grid.imagesLoaded ().progress (function ( ) { $grid.masonry (); }); } galleryBottom (); } $(document ).ready (function ( ) { initGallery (); });
function galleryBottom()
Javascript 代码如下:
对于移动端,获取当前屏幕中间的图片 title
,显示到底边栏中。
对于电脑端,当鼠标移动到某张图片上时,将对应的 title
显示到底边栏中。
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 function galleryBottom ( ){ if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i .test (navigator.userAgent )) { var descriptionContainer = document .querySelector ('.description-container span' ); document .addEventListener ("scroll" , function ( ) { var title = getBottomTitle (); descriptionContainer.textContent = title; }, 3000 ); } else { var galleryContent = document .querySelector ('.grid' ); var descriptionContainer = document .querySelector ('.description-container span' ); galleryContent.addEventListener ('mouseover' , function (event ) { if (event.target .tagName .toLowerCase () === 'img' ) { var title = event.target .getAttribute ('alt' ); descriptionContainer.textContent = title; } }); galleryContent.addEventListener ('mouseout' , function (event ) { if (event.target .tagName .toLowerCase () === 'img' ) { descriptionContainer.textContent = '' ; } }); } function getBottomTitle ( ) { var elements = document .querySelectorAll ('.fancybox' ); var viewportHeight = window .innerHeight ; var bottomElement = null ; for (var i = 0 ; i < elements.length ; i++) { var rect = elements[i].getBoundingClientRect (); if (rect.bottom <= viewportHeight && (!bottomElement || rect.bottom > bottomElement.rect .bottom )) { bottomElement = { element : elements[i], rect : rect } } } if (bottomElement) { return bottomElement.element .title ; } } }
演示
大功告成,瀑布流相册的演示:
真是太棒了!其他功能,再慢慢设计,想到哪写到哪。
python 辅助
python 辅助生成 gallery 格式的相关信息:
1 2 3 4 import osfor file in os.listdir(r'D:XXX' ): print (' ' + r'- {title: "XXX", src: /XXX' + file + '}' )