// ==UserScript== // @name 豆瓣电影排行榜 // @namespace https://scriptcat.org/zh-CN/ // @version 1.2.7 // @description 豆瓣电影分类排行榜移动端适配:28类无缝切换,无限滚动加载,自动续接下一页。https://movie.douban.com/chart可使用 // @author 失心向南 // @match *://movie.douban.com/chart* // @match *://movie.douban.com/typerank* // @match *://m.douban.com/movie/chart* // @grant GM_xmlhttpRequest // @connect movie.douban.com // @connect *.doubanio.com // @run-at document-start // @license MIT // ==/UserScript== (function () { 'use strict'; const vp = document.querySelector('meta[name="viewport"]'); if (vp) vp.setAttribute('content', 'width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'); const CATEGORIES = [ ['11', '剧情'], ['24', '喜剧'], ['5', '动作'], ['13', '爱情'], ['17', '科幻'], ['25', '动画'], ['10', '悬疑'], ['19', '惊悚'], ['20', '恐怖'], ['1', '纪录片'], ['23', '短片'], ['6', '情色'], ['14', '音乐'], ['7', '歌舞'], ['28', '家庭'], ['8', '儿童'], ['2', '传记'], ['4', '历史'], ['22', '战争'], ['3', '犯罪'], ['27', '西部'], ['16', '奇幻'], ['15', '冒险'], ['12', '灾难'], ['29', '武侠'], ['30', '古装'], ['18', '运动'], ['31', '黑色电影'], ]; let state = { type: '11', start: 0, isLast: false, loading: false, }; const style = document.createElement('style'); style.textContent = ` html, body { margin: 0 !important; padding: 0 !important; background: #f5f5f7 !important; } body > *:not(#db-root) { display: none !important; } #db-root { position: fixed; inset: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; background: #f5f5f7; } #db-root * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } #db-root .db-header { position: sticky; top: 0; z-index: 10; background: #fff; display: flex; align-items: center; justify-content: center; padding: max(12px, env(safe-area-inset-top)) 16px 12px; box-shadow: 0 1px 4px rgba(0,0,0,.06); } #db-root .db-title { font-size: 17px; font-weight: 700; color: #333; } #db-root .db-tabs { display: flex; gap: 8px; overflow-x: auto; padding: 10px 16px; background: #fff; scrollbar-width: none; border-bottom: 1px solid #f0f0f0; position: sticky; top: 49px; z-index: 9; } #db-root .db-tabs::-webkit-scrollbar { display: none; } #db-root .db-tabs button { flex: 0 0 auto; border: 0; background: #f0f0f3; padding: 7px 14px; border-radius: 18px; font-size: 13px; color: #666; cursor: pointer; white-space: nowrap; } #db-root .db-tabs button.active { background: #00b894; color: #fff; font-weight: 600; } #db-root .db-list { padding: 12px 12px 40px; } #db-root .db-movie { display: flex; gap: 12px; background: #fff; border-radius: 12px; padding: 12px; margin-bottom: 10px; cursor: pointer; } #db-root .db-movie img { width: 64px; height: 90px; border-radius: 6px; object-fit: cover; flex: 0 0 auto; background: #eee; } #db-root .db-info { flex: 1; min-width: 0; } #db-root .db-row { display: flex; align-items: center; gap: 8px; } #db-root .db-rank { font-size: 16px; font-weight: 800; color: #ccc; min-width: 24px; } #db-root .db-rank.r1 { color: #e74c3c; } #db-root .db-rank.r2 { color: #e67e22; } #db-root .db-rank.r3 { color: #f1c40f; } #db-root .db-name { font-size: 15px; font-weight: 700; color: #222; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #db-root .db-score { font-size: 15px; font-weight: 700; color: #00b894; flex: 0 0 auto; } #db-root .db-meta { font-size: 12px; color: #999; margin-top: 4px; } #db-root .db-actors { font-size: 12px; color: #aaa; margin-top: 4px; line-height: 1.5; } #db-root .db-sentinel { padding: 20px; text-align: center; color: #999; font-size: 13px; } `; let root = document.createElement('div'); root.id = 'db-root'; let observer = null; function doubanFetch(start) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `https://movie.douban.com/j/chart/top_list?type=${state.type}&interval_id=100:90&action=&start=${start}&limit=20`, headers: { 'Referer': 'https://movie.douban.com/', 'Accept': 'application/json' }, onload: (r) => { try { resolve(JSON.parse(r.responseText)); } catch (e) { reject(e); } }, onerror: reject, }); }); } function renderMovies(movies) { return movies.map(m => `
${m.rank} ${m.title} ${m.score ? m.score + '分' : ''}
${(m.regions || []).join(' / ')} · ${(m.types || []).join(' / ')} · ${(m.release_date || '').slice(0, 4)}
${(m.actors || []).slice(0, 4).join(' / ')}
`).join(''); } function render() { root.innerHTML = `
豆瓣电影榜
${CATEGORIES.map(([id, name]) => `` ).join('')}
加载中…
`; // 确保有且仅有一个 active const buttons = root.querySelectorAll('[data-type]'); buttons.forEach(x => x.classList.toggle('active', x.dataset.type === state.type)); const activeBtn = root.querySelector('.db-tabs button.active'); if (activeBtn) activeBtn.scrollIntoView({ inline: 'center', block: 'nearest' }); root.querySelectorAll('[data-type]').forEach(b => b.onclick = () => { if (b.dataset.type === state.type) return; state.type = b.dataset.type; root.querySelectorAll('[data-type]').forEach(x => x.classList.toggle('active', x.dataset.type === state.type)); b.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); load(true); }); } async function load(reset) { if (reset) { state.start = 0; state.isLast = false; const list = root.querySelector('.db-list'); list.innerHTML = '
加载中…
'; } try { const data = await doubanFetch(state.start); state.isLast = data.length < 20; const list = root.querySelector('.db-list'); const sentinel = list.querySelector('.db-sentinel'); sentinel.insertAdjacentHTML('beforebegin', renderMovies(data)); if (reset) root.scrollTop = 0; sentinel.textContent = state.isLast ? '— 没有更多了 —' : ''; bindMovies(); if (reset) setupScroll(); } catch (e) { const list = root.querySelector('.db-list'); const sentinel = list.querySelector('.db-sentinel'); if (sentinel) { sentinel.style.color = '#e74c3c'; sentinel.textContent = '加载失败,点此重试'; sentinel.onclick = () => { sentinel.onclick = null; sentinel.style.color = ''; load(false); }; } } } function bindMovies() { root.querySelectorAll('.db-movie').forEach(el => el.onclick = () => { const id = el.dataset.id; const isLandscape = window.innerWidth > window.innerHeight; location.href = isLandscape ? `https://movie.douban.com/subject/${id}/` : `https://m.douban.com/movie/subject/${id}/`; }); } function setupScroll() { if (observer) observer.disconnect(); const list = root.querySelector('.db-list'); if (!list) return; const sentinel = list.querySelector('.db-sentinel'); if (!sentinel) return; observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting && !state.loading && !state.isLast) { state.loading = true; state.start += 20; load(false).finally(() => { state.loading = false; }); } }, { rootMargin: '300px' }); observer.observe(sentinel); } function init() { document.head.appendChild(style); document.body.appendChild(root); render(); load(true).then(() => setupScroll()); } if (document.body) init(); else window.addEventListener('DOMContentLoaded', init); })();