
Code ini adalah script navigasi chapter manga menggunakan tombol Next dan Prev untuk blog berbasis Blogger. Script ini akan mengambil semua postingan dalam label tertentu, lalu membuat navigasi dinamis antar chapter.
Demo
Fitur
- daftar chapter.
- tombol Next, Prev, dan dropdown untuk langsung memilih chapter.
- Support prefetch konten agar loading lebih cepat.
Cara Menggunakan
- Pastikan setiap chapter memiliki label yang sama (misalnya: Overlord).
- Pasang "CSS" di atas </b:skin>
- Pasang "HTML" di dalam postingan chapter lewat Edit thema, letakan tepat diatas container Chapter dan pasang id #output_chapter pada container chapter
- Pasang "MainScript" di atas </body>
Kode CSS
.nextprevJs_navigasi{--button-bg:#7289DA;--button-hover-bg:#5469B1;--text-color:#E4E6EB;--border-radius:5px;--button-padding:5px 9px;--select-bg:#2C2F33;--select-color:#E4E6EB;display:flex;justify-content:space-between;align-items:center;margin:10px 0}.nextprevJs_navigasi select{background:var(--select-bg);color:var(--select-color);border:1px solid #555;border-radius:var(--border-radius);padding:var(--button-padding)}.nextprevJs_navigasi a{text-decoration:none;background:var(--button-bg);color:var(--text-color);padding:var(--button-padding);border-radius:var(--border-radius);margin:0 5px;display:inline-block}.nextprevJs_navigasi a:hover{background:var(--button-hover-bg)}#output_chapter img{width:100%;height:auto}#output_chapter .skeleton{width:100%;height:300px;background:linear-gradient(90deg,silver 25%,#d9d9d9 50%,silver 75%);background-size:200% 100%;animation:1.5s infinite skeleton-loading;border-radius:4px;margin:10px 0}@keyframes skeleton-loading{0%{background-position:200% 0}100%{background-position:-200% 0}}
Kode HTML
<b:with value='["Chapter", "NovelChapter", "MangaChapter"]' var='checkLabel'>
<b:if cond='data:post.labels any (i => i.name in data:checkLabel)'>
<b:loop values='data:post.labels filter (i => i.name not in data:checkLabel)' var='l'>
<div class="nextJs" expr:data-label='data:l.name'></div>
</b:loop>
</b:if>
</b:with>
Kode MainScript
<script>
/*<![CDATA[*/
class MangaNav{static globalArr=null;static globalCache={};static instances=[];static prefetchedUrls={};static _hasPopstate=!1;constructor(e){this.config=e,this.arr=[],this.cache=MangaNav.globalCache,this.nextUrl=null,this.prevUrl=null,this.homeUrl=null,this.instanceId=Math.random().toString(36).substr(2,9),MangaNav.instances.push(this)}run(){let e=this.config.selector;this.tag_label=document.querySelector(e.tag_label),this.tag_label&&(this.config.cat=this.tag_label.dataset.label,this.outputEls=Array.from(document.querySelectorAll(e.output)),this.outputEls.length&&(this.outputEls.forEach(e=>this.createControls(e)),MangaNav.globalArr?this.buildGlobal():this.xhr(),window.addEventListener("popstate",()=>{let e=location.pathname,t=MangaNav.globalArr?.find(t=>new URL(t.url).pathname===e);t&&MangaNav.instances.forEach(e=>e.loadImages(t.url))})))}createControls(e){let t=document.createElement("div");t.innerHTML=this.config.html.nav,e.appendChild(t.firstElementChild);let r=e.querySelector(this.config.selector.select);r.addEventListener("change",e=>this.loadImages(e.target.value))}xhr(){let{site:e,max:t,textError:r}=this.config,a=encodeURIComponent(this.config.cat),l=`${e}/feeds/posts/summary/-/${a}?alt=json&max-results=${t}`,s=new URL(e||location.origin).origin===location.origin;(s?fetch(`${l}&start-index=1`).then(e=>e.json()):this.jsonpRequest(`${l}&start-index=1`)).then(e=>{let r=+e.feed.openSearch$totalResults.$t,a=Math.ceil(r/t),n=[];for(let i=0;i<a;i++){let h=`${l}&start-index=${i*t+1}`;n.push(s?fetch(h).then(e=>e.json()):this.jsonpRequest(h))}return Promise.all(n)}).then(e=>{MangaNav.globalArr=e.flatMap(e=>e.feed.entry||[]).map(e=>({title:e.title.$t,date:e.published.$t,url:e.link.find(e=>"alternate"===e.rel).href,categories:e.category?.map(e=>e.term)||[]})).sort((e,t)=>new Date(t.date)-new Date(e.date)),this.buildGlobal()}).catch(()=>this.showError(r))}jsonpRequest(e){return new Promise((t,r)=>{let a=`cb_${Date.now()}`;window[a]=e=>{t(e),delete window[a],l.remove()};let l=document.createElement("script");l.src=`${e}&callback=${a}`,l.onerror=()=>{r(),delete window[a],l.remove()},document.body.appendChild(l)})}buildGlobal(){this.arr=MangaNav.globalArr.filter(e=>!e.categories.includes("Series")).reverse();let e=MangaNav.globalArr.find(e=>e.categories.includes("Series"));this.homeUrl=e?.url||MangaNav.globalArr[MangaNav.globalArr.length-1].url,this.processFeed()}processFeed(){let e=this.arr.map((e,t)=>{let r=e.title.match(/chapter\s*\d+(\.\d+)?/i),a=r?r[0].replace(/chapter/i,"Chapter"):`Chapter ${t+1}`;return`<option value="${e.url}">${a}</option>`}).join(""),t=0,r=this.config.selector.select;this.outputEls.forEach(a=>{let l=a.querySelector(r);l.innerHTML=e;let s=this.arr.findIndex(e=>new URL(e.url).pathname===location.pathname);t=s>-1?s:0,l.selectedIndex=t}),this.loadImages(this.arr[t].url)}renderNextPrevLinks(){[this.prevUrl,this.nextUrl].forEach(e=>{e&&!MangaNav.prefetchedUrls[e]&&(MangaNav.prefetchedUrls[e]=!0,this.loadImages(e,!0))});let e=this.prevUrl?`<a href="#" data-url="${this.prevUrl}" class="prev-link">Prev</a>`:"",t=`<a href="${this.homeUrl}" class="home-link">Home</a>`,r=this.nextUrl?`<a href="#" data-url="${this.nextUrl}" class="next-link">Next</a>`:"",a=`${e} ${t} ${r}`;this.outputEls.forEach(e=>{let t=e.querySelector(this.config.selector.nav_box);t.innerHTML=a,t.querySelectorAll("a[data-url]").forEach(e=>{e.onclick=t=>{t.preventDefault(),this.syncAll(e.dataset.url)}})})}syncAll(e){MangaNav.instances.forEach(t=>t.loadImages(e))}loadImages(e,t=!1){let r=document.querySelector(this.config.selector.output_chapter);if(r&&!t&&(r.innerHTML=[,,,,,].fill(this.config.html.skeleton).join("")),this.cache[e]){t||(this.renderImages(this.cache[e]),this.updateState(e));return}fetch(`${e}`).then(e=>e.text()).then(r=>{let a=new DOMParser().parseFromString(r,"text/html"),l=Array.from(a.querySelectorAll(this.config.selector.postbody)),s=l.map(e=>e.outerHTML);this.cache[e]=s,t||(this.renderImages(s),this.updateState(e))}).catch(()=>this.showError(this.config.textError))}prefetchImages(e){if(!e)return;let t=document.createElement("a");t.href=e;let r=new Image;r.src=t.href}updateState(e){history.pushState(null,"",new URL(e).pathname),this.outputEls.forEach(t=>{let r=t.querySelector(this.config.selector.select);r&&(r.value=e)});let t=this.arr.findIndex(t=>new URL(t.url).pathname===new URL(e).pathname);this.nextUrl=this.arr[t+1]?.url||null,this.prevUrl=this.arr[t-1]?.url||null,this.prefetchImages(this.nextUrl),this.renderNextPrevLinks(),this.scrollToOutputChapter()}renderImages(e){let t=document.querySelector(this.config.selector.output_chapter);if(!t)return;let r=document.createDocumentFragment();e.forEach(e=>{let t=document.createElement("div");t.innerHTML=e,r.appendChild(t.firstElementChild)}),t.innerHTML="",t.appendChild(r)}scrollToOutputChapter(){let e=document.querySelector(this.config.selector.output_chapter);e&&e.scrollIntoView({behavior:"smooth",block:"start"})}showError(e){let t=document.querySelector(this.config.selector.output_chapter);t&&(t.textContent=e)}}
document.addEventListener('DOMContentLoaded', () => {
const configNextprev = {
max: 30,
site: "",
selector: {
tag_label: '.nextJs',
output: '.nextJs',
postbody: '#output_chapter img',
output_chapter: '#output_chapter',
select: '.chapter-select',
nav_box: '.nextprev_ouput'
},
html: {
nav: `
<div class="manganav">
<div class="nextprevJs_navigasi">
<select class="chapter-select"></select>
<div class="nextprev_ouput"></div>
</div>
</div>
`,
skeleton: `<div class="skeleton"></div>`
},
textError: 'Error loading chapter'
};
new MangaNav(configNextprev).run();
});
/*]]>*/
</script>
Selamat mencoba dan semoga bermanfaat!
Apa ada live demo untuk skrip ini?
karena live demo pakai proxy jadi agak lambat dibandingkan tanpa proxy.
kalau ada kabel lain seperti "project" dll
bisa ditambahkan ke dalam sini
<b:with value='["Chapter", "NovelChapter", "MangaChapter"]' var='checkLabel'>
<b:with value='["Chapter", "Sub"]' var='checkLabel'>
Jangan masukan label One Piece disitu.