Langsung ke konten utama

Next & Prev Chapter Manga

author profile

MAGIC Reincarnated

Web Developer
Banner

Next & Prev Chapter Manga

Next & Prev Chapter Manga

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

LIVE DEMO

Fitur

  • daftar chapter.
  • tombol Next, Prev, dan dropdown untuk langsung memilih chapter.
  • Support prefetch konten agar loading lebih cepat.

Cara Menggunakan

  1. Pastikan setiap chapter memiliki label yang sama (misalnya: Overlord).
  2. Pasang "CSS" di atas </b:skin>
  3. Pasang "HTML" di dalam postingan chapter lewat Edit thema, letakan tepat diatas container Chapter dan pasang id #output_chapter pada container chapter
  4. 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!

"Bahkan batu pun bisa bernyanyi jika disentuh oleh cahaya bulan."

“The journey continues in the next chapter...”

Komentar Pembaca

6 Komentar
  1. #Question...
    Wernayasa
    Ok, maksih.
    Apa ada live demo untuk skrip ini?
  2. #Reply... Wernayasa
    MAGIC Reincarnated
    sudah ada sekarang, kucantumkan dibawah iframe.
    karena live demo pakai proxy jadi agak lambat dibandingkan tanpa proxy.
  3. #Question...
    Badi
    Maximal 2 Label ya min?
  4. #Reply... Badi
    MAGIC Reincarnated
    label di chapter? bisa lebih dari dua
    kalau ada kabel lain seperti "project" dll

    bisa ditambahkan ke dalam sini
    <b:with value='["Chapter", "NovelChapter", "MangaChapter"]' var='checkLabel'>
  5. Badi
    Postnya 3 label, Misalnya "Chapter,One Piece,Sub" error bg
  6. #Reply... Badi
    Wernayasa
    label Chapter dan Sub masukkan ke dalam var='checkLabel'
    <b:with value='["Chapter", "Sub"]' var='checkLabel'>
    Jangan masukan label One Piece disitu.
Tinggalkan komentar Jika suka dengan kontent ini
Masukkan URL Gambar (imgbb) / (im.ge) atau Potongan Kode, atau Quote, lalu klik tombol yang kamu inginkan untuk di-parse. Salin hasil parse lalu paste ke kolom komentar.


image quote pre code

Recent Komentar

Animedayid

terimakasih panduannya

MAGIC Reincarnated

kode mainscript letaknya head, tepatnya bisa diatas </head>

MAGIC Reincarnated

kode mainscript letaknya head, tepatnya bisa diatas . cara untuk implementasiny...

MAGIC Reincarnated

dihentikan sementara. sampai ada waktu, bakal ku dikembangkan lagi kalau ada wak...

Animedayid

kalo di bloger ini tarunya di element mana ya?

Load More