update:优化图库加载
This commit is contained in:
parent
027db288a6
commit
0df7af216c
File diff suppressed because one or more lines are too long
|
@ -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%); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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}})})});
|
|
@ -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()
|
||||
$(document).ready(function(){
|
||||
const $grid = $('#image-grid').isotope({
|
||||
itemSelector: '.grid-item',
|
||||
percentPosition: true,
|
||||
masonry: {
|
||||
columnWidth: '.grid-item'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let allImages = [];
|
||||
let currentIndex = 0;
|
||||
const batchSize = 6;
|
||||
const baseUrl = ThemeConfig.blog_url; // 替换成您的API URL
|
||||
|
||||
function loadImages(callback) {
|
||||
// ...逻辑保持不变
|
||||
if (allImages.length) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
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`
|
||||
// 获取所有图片
|
||||
const apiUrl = baseUrl + '/apis/api.plugin.halo.run/v1alpha1/plugins/PluginPhotos/photos';
|
||||
$.getJSON(apiUrl, function(data) {
|
||||
allImages = data.items; // 假设这是图片数组
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
resize(){
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(window.sortableResize)
|
||||
window.sortableResize = setTimeout(() => {
|
||||
this.winWidth = window.innerWidth
|
||||
this._setBlocWidth(()=>{
|
||||
this.orderelements()
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
// 将图片元素添加到网格中并重新布局
|
||||
$grid.append(items)
|
||||
.isotope('appended', items)
|
||||
.imagesLoaded().progress(function() {
|
||||
$grid.isotope('layout');
|
||||
});
|
||||
// ...其余逻辑保持不变
|
||||
// 如果所有图片都已加载,可以选择隐藏加载更多按钮
|
||||
if (currentIndex >= allImages.length) {
|
||||
$domLoad.remove()
|
||||
ob.unobserve(loading)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
init(){
|
||||
let {parent, links, active} = this
|
||||
|
||||
links.forEach((el, id) => {
|
||||
if(id === 0){
|
||||
el.classList.add(active)
|
||||
this.dataLink = el.dataset.sjslink
|
||||
// 初始加载
|
||||
loadBatchImages();
|
||||
const ob = new IntersectionObserver(entries => {
|
||||
if (entries[0].isIntersecting){
|
||||
loadBatchImages();
|
||||
}
|
||||
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);
|
||||
threshold:1
|
||||
})
|
||||
const loading = document.querySelector('.joe_loading')
|
||||
ob.observe(loading)
|
||||
|
||||
}
|
||||
|
||||
_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()
|
||||
}
|
||||
}
|
||||
_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(); // 调用重新布局方法
|
||||
};
|
||||
$('.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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
images.forEach(img => {
|
||||
observer.observe(img);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
_sumArrHeight(arr, col){
|
||||
return arr.reduce((acc, val, id)=>{
|
||||
let cle = id%col
|
||||
if(!acc[cle]){
|
||||
acc[cle] = 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
if(opacity >= 0){
|
||||
el.style.opacity = opacity
|
||||
requestAnimationFrame(animation)
|
||||
} else {
|
||||
el.style.opacity = 0
|
||||
el.style.display = 'none'
|
||||
if(callback){
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(animation)
|
||||
}
|
||||
}
|
||||
|
||||
HTMLElement.prototype.sortablejs = HTMLElement.prototype.sortablejs || function(params){
|
||||
return new Sortable({parent: this, ...params})
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue