240410 更新:调整Bar配置问题,修复多个相册页面造成的问题
240112 更新:新增Bar条切换相册分类;修复Bar栏重复点击的问题
如果没有服务器可以搭建memos,可以使用iCat自用的memos服务
效果预览 创建数据 新建 [blogRoot]/themes/butterfly/layout/includes/page/photo.pug
页面,并新增以下内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 if page.bar .icat-status-bar .status-bar-tips= page.bar.tips || '类别' .status-bar #bar-box each item in page.bar.list - const content = item.split(' || ') .status-bar-item a(onclick="photos('" + content[0] + "')") #{content[1]} #status-bar-button(onclick="statusbar()") i.iconfont.icat-chevron-too if page.bar.more - const contents = page.bar.more.split(' || ') if contents[0].startsWith('/') a.status-bar-more(href="javascript:void(0)" onclick="pjax.loadUrl('" + contents[0] + "')") #{contents[1]} else a.status-bar-more(href=contents[0]) #{contents[1]} div.gallery-photos.page img(src="https://img.meuicat.com/blog/loading.svg" style="margin:auto")
修改 [blogRoot]/themes/butterfly/layout/page.pug
来使页面匹配 ( + 号直接删除 即是正常缩进) 1 2 3 4 5 6 when 'categories' include includes/page/categories.pug + when 'photo' + include includes/page/photo.pug default include includes/page/default-page.pug
新建 [blogRoot]/themes/butterfly/source/css/_page/photo.styl
样式文件,并新增以下内容 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 .icat-status-bar margin : 16px 0 display : flex white-space : nowrap align-items : center background : var (--icat-card-bg) border-radius : 8px padding : 0 12px border : var (--style-border) transition : .6s box-shadow : var (--icat-shadow-border) &:hover border-color : var (--icat-blue) box-shadow : var (--icat-shadow-blue) .status-bar padding : 0.4rem 0 0.4rem 0.4rem white-space : nowrap overflow : hidden transition : .3s width : 100% justify-content : space-between user-select: none display : flex align-items : center font-size : 15px #bar-box white-space : nowrap overflow-x : scroll overflow-y : hidden display : flex border-radius : 8px align-items : center height : 30px .status-bar-item a padding : 0.1rem 0.5rem margin-right : 10px font-weight : 700 border-radius : 8px display : flex align-items : center height : 30px color : var (--icat-fontcolor) opacity : .8 transition : .6s &:hover background : var (--icat-blue) opacity : 1 color : var (--icat-white) &.selected a background : var (--icat-blue) !important opacity : 1 !important color : var (--icat-white) !important #status-bar-button margin-left : 12px cursor : pointer height : 22px display : flex align-items : center transition : .6s &:hover color : var (--icat-blue) .status-bar-more margin-left : 12px font-weight : 400 color : var (--icat-fontcolor) opacity : .8 transition : .6s &:hover color : var (--icat-blue) #bar-box ::-webkit-scrollbar display : none .gallery-photos width : 100% text-align : center animation : slide-in .6s .4s backwards .gallery-photo min-height : 5rem width : 24.99% padding : 4px position : relative animation : slide-in 0.6s 0.4s backwards +maxWidth1024 () width : 33.3% +maxWidth768 () width : 49.9% padding : 3px +minWidth2000 () width : 20% &:hover img transform : scale (1.1 ) a border-radius : 8px border : var (--style-border) box-shadow : var (--icat-shadow-border) display : block overflow : hidden transition : .6s &:hover border-color : var (--icat-blue) box-shadow : var (--icat-shadow-blue) img display : block width : 100% animation : fadeIn 1s cursor : pointer transition : all .4s ease-in-out .photo-title , .photo-time max-width : calc (100% - 7px ) line-height : 1.8 position : absolute left : 4px font-size : 14px background : rgba (0 ,0 ,0 ,0.3 ) padding : 0px 8px color : #fff animation : fadeIn 1s .photo-title bottom :4px border-radius : 0 8px 0 8px +maxWidth768 () font-size : 12px left : 3px bottom : 3px .photo-time top :4px border-radius : 8px 0 8px 0
可选CSS样式 新建 [blogRoot]/source/css/photo.css
样式文件,并新增以下内容 (也可以在自建的css文件里新增内容) 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 .icat-status-bar { margin : 16px 0 ; display : flex; white-space : nowrap; align-items : center; background : var (--icat-card-bg); border-radius : 8px ; padding : 0 12px ; border : var (--style-border); transition : 0.6s ; box-shadow : var (--icat-shadow-border); } .icat-status-bar :hover { border-color : var (--icat-blue); box-shadow : var (--icat-shadow-blue); } .icat-status-bar .status-bar { padding : 0.4rem 0 0.4rem 0.4rem ; white-space : nowrap; overflow : hidden; transition : 0.3s ; width : 100% ; justify-content : space-between; user-select: none; display : flex; align-items : center; font-size : 15px ; } .icat-status-bar .status-bar #bar-box { white-space : nowrap; overflow-x : scroll; overflow-y : hidden; display : flex; border-radius : 8px ; align-items : center; height : 30px ; } .icat-status-bar .status-bar #bar-box .status-bar-item a { padding : 0.1rem 0.5rem ; margin-right : 10px ; font-weight : 700 ; border-radius : 8px ; display : flex; align-items : center; height : 30px ; color : var (--icat-fontcolor); opacity : 0.8 ; transition : 0.6s ; } .icat-status-bar .status-bar #bar-box .status-bar-item a :hover { background : var (--icat-blue); opacity : 1 ; -ms-filter : none; filter : none; color : var (--icat-white); } .icat-status-bar .status-bar #bar-box .status-bar-item .selected a { background : var (--icat-blue) !important ; opacity : 1 !important ; -ms-filter : none !important ; filter : none !important ; color : var (--icat-white) !important ; } .icat-status-bar #status-bar-button { margin-left : 12px ; cursor : pointer; height : 22px ; display : flex; align-items : center; transition : 0.6s ; } .icat-status-bar #status-bar-button :hover { color : var (--icat-blue); } .icat-status-bar .status-bar-more { margin-left : 12px ; font-weight : 400 ; color : var (--icat-fontcolor); opacity : 0.8 ; transition : 0.6s ; } .icat-status-bar .status-bar-more :hover { color : var (--icat-blue); } #bar-box ::-webkit-scrollbar { display : none; } .gallery-photos { width :100% ; margin-top :16px ; text-align : center; } .gallery-photo { min-height :5rem ; width :24.99% ; padding :4px ; position :relative; } .gallery-photo a { border-radius :8px ; border : var (--style-border-always); box-shadow : var (--icat-shadow-border); display :block; overflow :hidden; } .gallery-photo img { display :block; width :100% ; animation :fadeIn 1s ; cursor :pointer; transition :all .4s ease-in-out !important ; } .gallery-photo span .photo-title ,.gallery-photo span .photo-time { max-width :calc (100% - 7px ); line-height :1.8 ; position :absolute; left :4px ; font-size :14px ; background :rgba (0 ,0 ,0 ,0.3 ); padding :0px 8px ; color :#fff ; animation :fadeIn 1s ; } .gallery-photo span .photo-title { bottom :4px ; border-radius :0 8px 0 8px ; } .gallery-photo span .photo-time { top :4px ; border-radius :8px 0 8px 0 ; } .gallery-photo :hover img { transform :scale (1.1 ); } @media screen and (max-width :1100px ) { .gallery-photo { width :33.3% ; } }@media screen and (max-width :768px ) { .gallery-photo { width :49.9% ; padding :3px } .gallery-photo span .photo-title { font-size :12px } .gallery-photo span .photo-title { left :3px ; bottom :3px ; } @keyframes fadeIn { 0% { opacity :0 ; } 100% { opacity :1 ; } }
在 _config.butterfly.yml
主题配置文件中 inject
下的 head
引入 photo.css
1 2 3 4 5 6 7 8 9 ··· inject: head: - <link rel="stylesheet" href="/css/photo.css"> bottom: - ··· ···
创建 [blogRoot]/source/js/memos/photo.js
文件,并新增以下内容,用来处理Memos动态相册的函数 (或写在自建的公共 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 84 85 86 87 88 89 90 91 92 93 94 95 96 function whenDOMReady ( ) { if (location.pathname == '/photos/' ) photos ('相册' ); } whenDOMReady ()document .addEventListener ("pjax:complete" , whenDOMReady)window .onresize = () => { if (location.pathname == '/photos/' ) waterfall ('.gallery-photos' ); }; function photos (tag ) { let apiUrl = `你的memos地址/api/v1/memo?creatorId=用户UID&tag=${tag} ` ; fetch (apiUrl).then (res => res.json ()).then (data => { let html = '' , imgs = [] data.forEach (item => { let ls = item.content .match (/\!\[.*?\]\(.*?\)/g ) if (ls) imgs = imgs.concat (ls) if (item.resourceList .length ) { item.resourceList .forEach (t => { if (t.externalLink ) imgs.push (`![](${t.externalLink} )` ) else imgs.push (`![](${url} /o/r/${t.id} /${t.publicId} /${t.filename} )` ) }) } }) if (imgs) imgs.forEach (item => { let img = item.replace (/!\[.*?\]\((.*?)\)/g , '$1' ), time, title, tat = item.replace (/!\[(.*?)\]\(.*?\)/g , '$1' ) if (tat.indexOf (' ' ) != -1 ) { time = tat.split (' ' )[0 ] title = tat.split (' ' )[1 ] } else title = tat html += `<div class="gallery-photo"><a href="${img} " data-fancybox="gallery" class="fancybox" data-thumb="${img} "><img class="no-lazyload photo-img" loading='lazy' decoding="async" src="${img} "></a>` title ? html += `<span class="photo-title">${title} </span>` : '' time ? html += `<span class="photo-time">${time} </span>` : '' html += `</div>` }) document .querySelector ('.gallery-photos.page' ).innerHTML = html imgStatus.watch ('.photo-img' , () => { waterfall ('.gallery-photos' ) }) window .Lately && Lately .init ({ target : '.photo-time' }) }).catch () if (document .querySelector (".icat-status-bar" )) { var statusBarItemItems = document .querySelectorAll ('.status-bar-item' ); let firstElement = statusBarItemItems[1 ]; firstElement.classList .add ('selected' ); Array .from (statusBarItemItems).forEach (function (element ) { element.onclick = function (event ) { var selectedElements = document .querySelectorAll ('.status-bar-item.selected' ); Array .from (selectedElements).forEach (function (selectedElement ) { selectedElement.classList .remove ('selected' ); }); element.classList .add ('selected' ); event.stopPropagation (); event.preventDefault (); return false ; }; }); } } function statusbar ( ) { var e; var t = document .getElementById ("bar-box" ); var o = document .getElementById ("status-bar-button" ); var n = t.clientWidth ; if (t) { if (t.scrollLeft + t.clientWidth >= t.scrollWidth - 8 ) { t.scroll ({ left : 0 , behavior : "smooth" }); } else { t.scrollBy ({ left : n, behavior : "smooth" }); } t.addEventListener ("scroll" , function n ( ) { clearTimeout (e); e = setTimeout (function ( ) { o.style .transform = t.scrollLeft + t.clientWidth >= t.scrollWidth - 8 ? "rotate(180deg)" : "" ; t.removeEventListener ("scroll" , n); }, 150 ); }); } };
创建 [blogRoot]/source/js/memos/imgStatus.min.js
文件,并新增以下内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ! function ( ) { this .loaded = 0 , this .failed = 0 , this .total = 0 , this .watch = function (a, b ) { var c = document .querySelectorAll (a); if (!c.length ) return console .log ("[imgStatus]: There aren't any images associated with this selector (" + a + ")!" ); this .total = c.length ; for (var d = 0 ; d < this .total ; d++) isCached (c[d].src ) ? this ._setLoaded (b) : c[d].addEventListener ? (c[d].addEventListener ("load" , this ._setLoaded .bind (this , b)), c[d].addEventListener ("error" , this ._setFailed .bind (this , b))) : (c[d].attachEvent ("onload" , this ._setLoaded .bind (this , b)), c[d].attachEvent ("onerror" , this ._setFailed .bind (this , b))) }, this .isCached = function (a ) { var b = new Image ; return b.src = a, b.complete }, this ._setFailed = function (a, b ) { ++this .failed , "function" == typeof a && a (this ) }, this ._setLoaded = function (a, b ) { ++this .loaded , "function" == typeof a && a (this ) }, this .isDone = function ( ) { return this .loaded + this .failed === this .total ? !0 : !1 }, "object" == typeof window && (window .imgStatus = this ) }();
创建 [blogRoot]/source/js/memos/lately.min.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 ! function ( ) { window .Lately = new function ( ) { var t = this ; this .lang = { second : "秒" , minute : "分钟" , hour : "小时" , day : "天" , month : "个月" , year : "年" , ago : "前" , error : "NaN" }; var e = function (e ) { e = new Date (n (e)); var r = new function ( ) { this .second = (Date .now () - e.getTime ()) / 1e3 , this .minute = this .second / 60 , this .hour = this .minute / 60 , this .day = this .hour / 24 , this .month = this .day / 30 , this .year = this .month / 12 }, i = Object .keys (r).reverse ().find (function (t ) { return r[t] >= 1 }); return (i ? function (t, e ) { return Math .floor (t) + e }(r[i], t.lang [i]) : t.lang .error ) + t.lang .ago }, n = function (t ) { return t = new Date (t && ("number" == typeof t ? t : t.replace (/-/g , "/" ).replace ("T" , " " ))), !isNaN (t.getTime ()) && t.getTime () }; return { init : function ( ) { var r = arguments .length > 0 && void 0 !== arguments [0 ] ? arguments [0 ] : {}, i = r.target , a = void 0 === i ? "time" : i, o = r.lang ; o && (t.lang = o); var u = !0 , h = !1 , l = void 0 ; try { for (var s, c = document .querySelectorAll (a)[Symbol .iterator ](); !(u = (s = c.next ()).done ); u = !0 ) { var f = s.value , g = n (f.dateTime ) || n (f.title ) || n (f.innerHTML ) || 0 ; if (!g) return ; f.title = new Date (g).toLocaleString (), f.innerHTML = e (g) } } catch (t) { h = !0 , l = t } finally { try { !u && c. return &&c. return () } finally { if (h) throw l } } }, format : e } } }();
创建 [blogRoot]/source/js/memos/waterfall.min.js
文件,并新增以下内容,用来处理Memos动态相册的瀑布流 (在上几节的即刻短文教程里添加了瀑布流的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 function waterfall (a ) { function b (a, b ) { var c = window .getComputedStyle (b); return parseFloat (c["margin" + a]) || 0 } function c (a ) { return a + "px" } function d (a ) { return parseFloat (a.style .top ) } function e (a ) { return parseFloat (a.style .left ) } function f (a ) { return a.clientWidth } function g (a ) { return a.clientHeight } function h (a ) { return d (a) + g (a) + b ("Bottom" , a) } function i (a ) { return e (a) + f (a) + b ("Right" , a) } function j (a ) { a = a.sort (function (a, b ) { return h (a) === h (b) ? e (b) - e (a) : h (b) - h (a) }) } function k (b ) { f (a) != t && (b.target .removeEventListener (b.type , arguments .callee ), waterfall (a)) } "string" == typeof a && (a = document .querySelector (a)); var l = [].map .call (a.children , function (a ) { return a.style .position = "absolute" , a }); a.style .position = "relative" ; var m = []; l.length && (l[0 ].style .top = "0px" , l[0 ].style .left = c (b ("Left" , l[0 ])), m.push (l[0 ])); for (var n = 1 ; n < l.length ; n++) { var o = l[n - 1 ], p = l[n], q = i (o) + f (p) <= f (a); if (!q) break ; p.style .top = o.style .top , p.style .left = c (i (o) + b ("Left" , p)), m.push (p) } for (; n < l.length ; n++) { j (m); var p = l[n], r = m.pop (); p.style .top = c (h (r) + b ("Top" , p)), p.style .left = c (e (r)), m.push (p) } j (m); var s = m[0 ]; a.style .height = c (h (s) + b ("Bottom" , s)); var t = f (a); window .addEventListener ? window .addEventListener ("resize" , k) : document .body .onresize = k }
在 _config.butterfly.yml
主题配置文件中 inject
下的 head
和 bottom
分别引入 waterfall.min.js
lately.min.js
imgStatus.min.js
photo.js
1 2 3 4 5 6 7 8 9 10 11 12 ··· inject: head: - ··· bottom: - <script type="text/javascript" src="/js/memos/waterfall.min.js"></script> - <script type="text/javascript" src="/js/memos/imgStatus.min.js"></script> - <script type="text/javascript" src="/js/memos/lately.min.js"></script> - <script type="text/javascript" src="/js/memos/photo.js"></script> ···
创建并修改 [blogRoot]/source/photos/index.md
页面 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 --- title: 生活相册 date: 2023-03-14 21:47:47 type: photo top_img: false aside: false top_ page: truetop_bg: https://img.meuicat.com/banner top_ item: 相册top_title: 快门の色彩瞬间 top_ tips: 活在当下 热烈且自由comments: false bar: tips: 年份类别 list: - 相册 || 全部 - 2023 || 2023 - 2022 || 2022 - 2021 || 2021 - 2020 || 2020 - 2019 || 2019 - 2018 || 2018 - 2017 || 2017 more: /album/ || 影集 --- <!-- 页面内容 -->
参数 类型 释义 bar 可选 是否使用Bar分类栏 bar.tips 可选 Bar分类栏的提示文字 bar.list 必选 Bar分类栏的tag和显示文字,第一个参数为tag;第二个参数为需要显示的文字 bar.more 可选 Bar分类栏更多按钮的跳转链接和显示文字,第一个参数为跳转链接地址(支持/album/
、https://meuicat.com
这种格式,不支持不带头标的meuicat.com
格式;第二个参数为需要显示的文字
使用参数 1 2 3 4 5 6 7 8 9 #相册 <!-- 写法就是markdown的写法,中括号里先写时间再写标题,中间使用空格隔开 --> ![2023-01-29 我是标题 ](图片链接 ) <!-- 若不想要时间只写标题即可 --> ![我是标题 ](图片链接 ) <!-- 若不想要标题只写时间即可,只不过后面需要添加空格 --> ![2023-01-29 ](图片链接 ) <!-- 也可以只填写图片链接 --> ![](图片链接 )
1 2 3 4 5 #相册 ![2023-02-09 ](https://s11.ax1x.com/2023/03/16/pp3jQ3V.jpg ) ![ 犹豫没要微信 ](https://s11.ax1x.com/2023/03/17/ppGlZid.jpg ) ![2022-10-12 可可爱爱没有脑袋 ](https://s11.ax1x.com/2023/03/16/pp34dpj.jpg ) ![](https://s11.ax1x.com/2023/03/15/pp39iRI.jpg )
魔改适配 已适配Solitude主题,具体魔改教程可前往下方文章查看
Solitude魔改教程:iCat同款动态相册页
用照片来定格住最好的自己;是时光流逝的见证者,亦是记录者