update:优化图库加载

This commit is contained in:
jiewenhuang 2023-11-06 15:52:28 +08:00
parent 027db288a6
commit 0df7af216c
5 changed files with 124 additions and 252 deletions

File diff suppressed because one or more lines are too long

View File

@ -609,6 +609,7 @@ hr {
.wrapper {
padding: 40px 0;
}
.card {
@ -711,5 +712,38 @@ hr {
}
}
}
.grid {
position: relative;
margin: 0 auto;
max-width: 1200px;
.grid-item {
margin-bottom: 15px;
box-sizing: border-box;
padding: 5px;
height: auto; /* 预设高度可以改善布局的稳定性 */
img {
width: 100%;
height: auto; /* 保持图片宽高比 */
display: block;
object-fit: cover; /* 防止图片变形 */
border-radius: var(--radius-wrap);
}
}
/* 电脑大屏幕一行4张 */
@media (min-width: 1200px) {
.grid-item { width: calc(25%); }
}
@media (min-width: 768px) and (max-width: 1199px) {
.grid-item { width: calc(33.333%); }
}
@media (max-width: 767px) {
.grid-item { width: calc(50%); }
}
}

View File

@ -1 +1 @@
class Sortable{constructor({parent:t,links:e=document.querySelectorAll("[data-sjslink]"),active:s="active",margin:i=20,responsive:r={980:{columns:3},480:{columns:2},0:{columns:1}},fadeDuration:n={in:300,out:0}}={}){this.parent=t,this.links=Array.from(e),this.active=s,this.margin=i,this.responsive=r,this.fadeDuration=n,this.elements=Array.from(this.parent.children),this.activeElements=this.elements,this.columns=1,this.dataLink="all",this.winWidth=window.innerWidth,this.init()}orderelements(){let{parent:t,activeElements:e,columns:n,blocWidth:a,margin:l}=this;var s=e.reduce((t,e,s)=>{var i=this._sumArrHeight(t,n),r=s%n*(a+l),i=0<=s-n?i[s%n]+l*Math.floor(s/n):0;return e.style.transform=`translate3d(${r}px, ${i}px, 0)`,t.push(e.offsetHeight),t},[]),s=this._sumArrHeight(s,n),s=Math.max(...s)+l*(Math.floor(e.length/n)-1);t.style.height=s+"px"}handleFilterClick(t,e){t.preventDefault();let{links:s,active:i}=this;e.dataset.sjslink!==this.dataLink&&(this.dataLink=e.dataset.sjslink,s.forEach(t=>{t.isEqualNode(e)?t.classList.add(i):t.classList.remove(i)}),this._filterElements(()=>{this.orderelements()}))}resize(){window.addEventListener("resize",()=>{clearTimeout(window.sortableResize),window.sortableResize=setTimeout(()=>{this.winWidth=window.innerWidth,this._setBlocWidth(()=>{this.orderelements()})},500)})}init(){let{parent:t,links:e,active:s}=this;e.forEach((e,t)=>{0===t&&(e.classList.add(s),this.dataLink=e.dataset.sjslink),e.addEventListener("click",t=>{this.handleFilterClick(t,e)})}),this._setBlocWidth(),window.addEventListener("load",()=>{this._filterElements(()=>{this.orderelements()}),t.style.opacity=1}),this.resize();const i=new IntersectionObserver(t=>{t.forEach(t=>{var e;t.isIntersecting&&(e=(t=t.target).getAttribute("data-src"),t.setAttribute("src",e),i.unobserve(t))})},{threshold:.5});document.querySelectorAll("img.card__picture").forEach(t=>{i.observe(t)})}_setBlocWidth(t){var{parent:e,elements:s,margin:i,responsive:r}=this,r=this.columns=this._columnsCount(r).columns;let n=this.blocWidth=(e.clientWidth-i*(r-1))/r;s.forEach(t=>{t.style.width=n+"px"}),t&&t()}_filterElements(t){let{elements:e,dataLink:s,fadeDuration:i}=this;this.activeElements=e.filter(t=>"all"!==s&&t.dataset.sjsel!==s?(this._fadeOut(t,i.out),!1):(this._fadeIn(t,i.in),this._lazyLoadImages(t),!0)),t&&t()}_lazyLoadImages(t){t=t.querySelectorAll('img[loading="lazy"]');const e=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&((t=t.target).src=t.dataset.src,t.removeAttribute("loading"),e.unobserve(t),t.onload=()=>{this.orderelements()})})});t.forEach(t=>{e.observe(t)})}_sumArrHeight(t,i){return t.reduce((t,e,s)=>{s%=i;return t[s]||(t[s]=0),t[s]=t[s]+e,t},[])}_columnsCount(t){let s=this["winWidth"];return Object.entries(t).reduce((t,e)=>s>e[0]&&e[0]>=Math.max(t.width)?{width:e[0],columns:e[1].columns}:t,{width:0,columns:4})}_fadeIn(e,t=300,s){let i=parseFloat(window.getComputedStyle(e,null).getPropertyValue("opacity")),r=16/t;e.style.display="block",requestAnimationFrame(function t(){(i+=r)<=1?(e.style.opacity=i,requestAnimationFrame(t)):(e.style.opacity=1,s&&s())})}_fadeOut(e,t=300,s){let i=parseFloat(window.getComputedStyle(e,null).getPropertyValue("opacity")),r=t?16/t:1;requestAnimationFrame(function t(){0<=(i-=r)?(e.style.opacity=i,requestAnimationFrame(t)):(e.style.opacity=0,e.style.display="none",s&&s())})}}HTMLElement.prototype.sortablejs=HTMLElement.prototype.sortablejs||function(t){return new Sortable({parent:this,...t})};
$(document).ready(function(){const s=$("#image-grid").isotope({itemSelector:".grid-item",percentPosition:!0,masonry:{columnWidth:".grid-item"}});let n=[],a=0;const r=ThemeConfig.blog_url;function t(){const i=$(".joe_loading");var t,e;t=function(){for(var e=a+6,t=[];a<e&&a<n.length;a++){var o=n[a],o=$('<div class="grid-item wow fadeIn" data-sjsel="'+o.spec.groupName+'"><div class="card__picture"><a class="item animated wow jg-entry" href="'+o.spec.url+'" data-fancybox="gallery"><img src="'+r+o.spec.url+'" alt="'+o.spec.displayName+'"/></a></div></div>');t.push(o[0])}s.append(t).isotope("appended",t).imagesLoaded().progress(function(){s.isotope("layout")}),a>=n.length&&(i.remove(),l.unobserve(c))},n.length?t():(e=r+"/apis/api.plugin.halo.run/v1alpha1/plugins/PluginPhotos/photos",$.getJSON(e,function(e){n=e.items,t()}))}t();const l=new IntersectionObserver(e=>{e[0].isIntersecting&&t()},{threshold:1}),c=document.querySelector(".joe_loading");l.observe(c),$(".joe_photos__filter li").on("click",function(){let t=$(this).attr("data-sjslink");console.log(t),$(this).addClass("active").siblings().removeClass("active"),s.isotope({filter:function(){var e=$(this).attr("data-sjsel");return console.log("Filtering:",e,t),"*"===t||e===t}})})});

View File

@ -1,258 +1,102 @@
class Sortable {
constructor({
parent,
links = document.querySelectorAll('[data-sjslink]'),
active = 'active',
margin = 20,
responsive = {
980: {
columns: 3
},
480: {
columns: 2
},
0: {
columns: 1
}
},
fadeDuration = {
in: 300,
out: 0
}
} = {}) {
this.parent = parent
this.links = Array.from(links)
this.active = active
this.margin = margin
this.responsive = responsive
this.fadeDuration = fadeDuration
this.elements = Array.from(this.parent.children)
this.activeElements = this.elements
this.columns = 1
this.dataLink = 'all'
this.winWidth = window.innerWidth
this.init()
}
orderelements(){
let {parent, activeElements, columns, blocWidth, responsive, margin} = this
let arrayRectHeight = activeElements.reduce((acc, el, id) => {
let columnsHeight = this._sumArrHeight(acc, columns)
let positionX = (id%columns) * (blocWidth + margin)
let rectHeight = (id - columns >= 0) ? (columnsHeight[id%columns] + (margin * Math.floor(id / columns))) : 0
el.style.transform = `translate3d(${positionX}px, ${rectHeight}px, 0)`
acc.push(el.offsetHeight)
return acc
}, [])
let columnsMaxHeight = this._sumArrHeight(arrayRectHeight, columns)
let parentHeight = Math.max(...columnsMaxHeight) + (margin * (Math.floor(activeElements.length / columns) - 1))
parent.style.height = `${parentHeight}px`
}
handleFilterClick(ev, element){
ev.preventDefault()
let {links, active} = this
if(element.dataset.sjslink === this.dataLink){
return
} else {
this.dataLink = element.dataset.sjslink
links.forEach(el => {
el.isEqualNode(element) ? el.classList.add(active) : el.classList.remove(active)
})
this._filterElements(()=>{
this.orderelements()
})
$(document).ready(function(){
const $grid = $('#image-grid').isotope({
itemSelector: '.grid-item',
percentPosition: true,
masonry: {
columnWidth: '.grid-item'
}
}
});
resize(){
window.addEventListener('resize', () => {
clearTimeout(window.sortableResize)
window.sortableResize = setTimeout(() => {
this.winWidth = window.innerWidth
this._setBlocWidth(()=>{
this.orderelements()
})
}, 500)
})
}
init(){
let {parent, links, active} = this
let allImages = [];
let currentIndex = 0;
const batchSize = 6;
const baseUrl = ThemeConfig.blog_url; // 替换成您的API URL
links.forEach((el, id) => {
if(id === 0){
el.classList.add(active)
this.dataLink = el.dataset.sjslink
}
el.addEventListener('click', ev => {
this.handleFilterClick(ev, el)
})
})
this._setBlocWidth()
window.addEventListener('load', () => {
this._filterElements(()=>{
this.orderelements()
})
parent.style.opacity = 1
})
this.resize()
//使用Intersection Observer API实现懒加载
const ob = new IntersectionObserver(
(entries)=>{
entries.forEach((entry)=>{
if(entry.isIntersecting){
const img = entry.target;
const src = img.getAttribute('data-src');
img.setAttribute('src',src);
ob.unobserve(img);
}
})
},{
threshold:0.5
});
const imgs = document.querySelectorAll('img.card__picture');
imgs.forEach((img)=>{
ob.observe(img);
})
}
_setBlocWidth(callback){
let {parent, elements, margin, responsive} = this
let columns = this.columns = this._columnsCount(responsive)['columns']
let blocWidth = this.blocWidth = (parent.clientWidth - (margin * (columns - 1))) / columns
elements.forEach(el=>{
el.style.width = `${blocWidth}px`
})
if(callback){
callback()
function loadImages(callback) {
// ...逻辑保持不变
if (allImages.length) {
callback();
return;
}
}
_filterElements(callback){
let {elements, dataLink, fadeDuration} = this
this.activeElements = elements.filter(el => {
if(dataLink === 'all') {
this._fadeIn(el, fadeDuration.in)
this._lazyLoadImages(el);
return true
} else {
if(el.dataset.sjsel !== dataLink) {
this._fadeOut(el, fadeDuration.out)
return false
} else {
this._fadeIn(el, fadeDuration.in)
this._lazyLoadImages(el); // 添加这一行来启动懒加载
return true
}
}
})
if(callback){
callback()
}
}
// 添加一个新的方法来执行图片的懒加载,使用 IntersectionObserver
_lazyLoadImages(el) {
const images = el.querySelectorAll('img[loading="lazy"]');
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('loading');
observer.unobserve(img);
// 在图片加载完成后触发重新布局
img.onload = () => {
this.orderelements(); // 调用重新布局方法
};
}
});
});
images.forEach(img => {
observer.observe(img);
// 获取所有图片
const apiUrl = baseUrl + '/apis/api.plugin.halo.run/v1alpha1/plugins/PluginPhotos/photos';
$.getJSON(apiUrl, function(data) {
allImages = data.items; // 假设这是图片数组
callback();
});
}
_sumArrHeight(arr, col){
return arr.reduce((acc, val, id)=>{
let cle = id%col
if(!acc[cle]){
acc[cle] = 0
function loadBatchImages() {
const $domLoad = $('.joe_loading')
loadImages(function() {
// ...创建和插入元素的逻辑保持不变
const batchEndIndex = currentIndex + batchSize;
// 一批加载的图片项
const items = [];
// 获取当前批次的图片
for (; currentIndex < batchEndIndex && currentIndex < allImages.length; currentIndex++) {
const currentImage = allImages[currentIndex];
const item = $('<div class="grid-item wow fadeIn" data-sjsel="'+ currentImage.spec.groupName+'">' +
'<div class="card__picture">'+
'<a class="item animated wow jg-entry" href="'+ currentImage.spec.url+'" data-fancybox="gallery">'+
'<img src="' + baseUrl + currentImage.spec.url + '" alt="' + currentImage.spec.displayName + '"/>' +
'</a>'+
'</div>'+
'</div>');
items.push(item[0]);
}
acc[cle] = acc[cle]+val
return acc
}, [])
}
_columnsCount(obj){
let {winWidth} = this
return Object.entries(obj).reduce((acc, val)=>{
return winWidth > val[0] && val[0] >= Math.max(acc['width'])
? { width: val[0], columns: val[1]['columns'] }
: acc
}, {width: 0, columns: 4})
}
_fadeIn(el, duration = 300, callback){
let opacity = parseFloat(window.getComputedStyle(el, null).getPropertyValue("opacity")),
interval = 16,
gap = interval / duration
el.style.display = 'block'
function animation(){
opacity += gap
if(opacity <= 1){
el.style.opacity = opacity
requestAnimationFrame(animation)
} else {
el.style.opacity = 1
if(callback){
callback()
}
// 将图片元素添加到网格中并重新布局
$grid.append(items)
.isotope('appended', items)
.imagesLoaded().progress(function() {
$grid.isotope('layout');
});
// ...其余逻辑保持不变
// 如果所有图片都已加载,可以选择隐藏加载更多按钮
if (currentIndex >= allImages.length) {
$domLoad.remove()
ob.unobserve(loading)
}
});
}
// 初始加载
loadBatchImages();
const ob = new IntersectionObserver(entries => {
if (entries[0].isIntersecting){
loadBatchImages();
}
requestAnimationFrame(animation)
}
_fadeOut(el, duration = 300, callback){
let opacity = parseFloat(window.getComputedStyle(el, null).getPropertyValue("opacity")),
interval = 16,
gap = duration ? (interval / duration) : 1
function animation(){
opacity -= gap
}, {
threshold:1
})
const loading = document.querySelector('.joe_loading')
ob.observe(loading)
if(opacity >= 0){
el.style.opacity = opacity
requestAnimationFrame(animation)
} else {
el.style.opacity = 0
el.style.display = 'none'
if(callback){
callback()
}
$('.joe_photos__filter li').on('click', function(){
let filterValue = $(this).attr('data-sjslink');
console.log(filterValue)
// 添加active
$(this).addClass('active').siblings().removeClass('active');
$grid.isotope({
filter: function() {
// 这里 "this" 指的是每一个被 Isotope 处理的元素
// 检查 data-sjsel 属性值是否匹配我们筛选的值
const sjselValue = $(this).attr('data-sjsel'); // 这里获取的是.grid-item的data-sjsel
console.log('Filtering:', sjselValue, filterValue);
// 如果 filterValue 是 '*'(显示所有),或者 sjselValue 匹配筛选值,则保留元素
return filterValue === '*' || sjselValue === filterValue;
}
}
requestAnimationFrame(animation)
}
}
HTMLElement.prototype.sortablejs = HTMLElement.prototype.sortablejs || function(params){
return new Sortable({parent: this, ...params})
}
});
});
});

View File

@ -15,7 +15,7 @@
</div>
<nav class="joe_photos__filter">
<ul>
<li data-sjslink="all">
<li data-sjslink="*" class="active">
<a >全部</a>
</li>
<th:block th:each="group : ${groups}">
@ -30,16 +30,10 @@
</div>
<div class="wrapper">
<div id="sortable" class="sjs-default">
<div th:data-sjsel="${photo.spec.groupName}" th:each="photo : ${photoFinder.listAll()}">
<div class="card" >
<a class="item animated wow jg-entry" th:href="${photo.spec.url}" data-fancybox="gallery">
<img class="card__picture" loading="lazy" th:data-src="${photo.spec.url}" th:src="@{/assets/img/lazyload.gif}" th:alt="${photo.spec.displayName}">
</a>
</div>
</div>
<div class="grid " id="image-grid">
<!-- 预留空间 -->
</div>
<th:block th:replace="~{modules/macro/loading :: loading}" />
</div>