탭 이동 → iframe 방식으로 변경 (상단 1/3 영역)

- triggerLazyLoading, getImagesWithLazyLoad에 doc/win 파라미터 추가
  → iframe의 document/window로도 lazy 로딩 트리거 가능
- processEpisodeInFrame() 추가: iframe 생성 후 에피소드 순차 처리
- startQueue: window.location.href 이동 대신 iframe 생성/재사용
- processAutoQueue 유지 (기존 탭 방식 fallback)
- downloadCurrentEpisode에도 getImagesWithLazyLoad(document, window) 명시
This commit is contained in:
user01
2026-06-14 17:03:45 +09:00
parent b8ce964510
commit d570408f76

View File

@@ -237,17 +237,17 @@
}; };
// lazy loading 트리거: data-src 복사 + loading=eager + 스크롤 // lazy loading 트리거: data-src 복사 + loading=eager + 스크롤
const triggerLazyLoading = async () => { const triggerLazyLoading = async (doc, win) => {
const viewer = document.querySelector('.vw-imgs, [class*="vw-imgs"]') || document; const d = doc || document;
// 우선 현재 img들 강제 로딩 const w = win || window;
const viewer = d.querySelector('.vw-imgs, [class*="vw-imgs"]') || d;
forceLoadImages(viewer); forceLoadImages(viewer);
// 동적 스크롤: 새 콘텐츠가 계속 로드되면 scrollHeight도 계속 증가
let prevHeight = 0; let prevHeight = 0;
let stuckCount = 0; let stuckCount = 0;
while (stuckCount < 3) { while (stuckCount < 3) {
const maxScroll = Math.max( const maxScroll = Math.max(
document.documentElement.scrollHeight, d.documentElement.scrollHeight,
document.body.scrollHeight, 0 d.body.scrollHeight, 0
); );
if (maxScroll === prevHeight) { if (maxScroll === prevHeight) {
stuckCount++; stuckCount++;
@@ -255,21 +255,18 @@
stuckCount = 0; stuckCount = 0;
} }
prevHeight = maxScroll; prevHeight = maxScroll;
// 현재 위치에서 끝까지 step 단위로 스크롤 (느리게)
const step = Math.min(300, maxScroll); const step = Math.min(300, maxScroll);
for (let s = window.scrollY; s <= maxScroll; s += step) { for (let s = w.scrollY; s <= maxScroll; s += step) {
window.scrollTo(0, Math.min(s, maxScroll)); w.scrollTo(0, Math.min(s, maxScroll));
await sleep(300); await sleep(300);
} }
window.scrollTo(0, maxScroll); w.scrollTo(0, maxScroll);
// 스크롤 후 추가된 img들도 강제 로딩 (백그라운드 탭 IO 대체)
forceLoadImages(viewer); forceLoadImages(viewer);
await sleep(1500); await sleep(1500);
} }
// 끝까지 다 내려간 상태에서 여유 대기 + 최종 강제 로딩
forceLoadImages(viewer); forceLoadImages(viewer);
await sleep(2000); await sleep(2000);
window.scrollTo(0, 0); w.scrollTo(0, 0);
await sleep(1000); await sleep(1000);
}; };
@@ -309,20 +306,22 @@
log(`저장: ${filename} (${imageUrls.length}개)`); log(`저장: ${filename} (${imageUrls.length}개)`);
}; };
// 공통: 현재 페이지에서 이미지 획득 + lazy 로딩 트리거 // 공통: 페이지/iframe에서 이미지 획득 + lazy 로딩 트리거
const getImagesWithLazyLoad = async () => { const getImagesWithLazyLoad = async (doc, win) => {
let urls = getEpisodeImages(); const d = doc || document;
const w = win || window;
let urls = getEpisodeImages(d);
if (urls.length === 0) { if (urls.length === 0) {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
await sleep(500); await sleep(500);
urls = getEpisodeImages(); urls = getEpisodeImages(d);
if (urls.length > 0) break; if (urls.length > 0) break;
} }
} }
if (urls.length <= 3) { if (urls.length <= 3) {
log(`lazy loading 트리거 (${urls.length}개 → 전체)...`); log(`lazy loading 트리거 (${urls.length}개 → 전체)...`);
await triggerLazyLoading(); await triggerLazyLoading(d, w);
urls = getEpisodeImages(); urls = getEpisodeImages(d);
} }
return urls; return urls;
}; };
@@ -330,7 +329,7 @@
async function downloadCurrentEpisode() { async function downloadCurrentEpisode() {
try { try {
log('에피소드 다운로드 시작...'); log('에피소드 다운로드 시작...');
const imageUrls = await getImagesWithLazyLoad(); const imageUrls = await getImagesWithLazyLoad(document, window);
if (imageUrls.length === 0) { if (imageUrls.length === 0) {
alert('이미지를 찾을 수 없습니다.'); alert('이미지를 찾을 수 없습니다.');
@@ -372,14 +371,68 @@
return episodes.length > 0 ? episodes : null; return episodes.length > 0 ? episodes : null;
}; };
// 큐 저장 후 첫 화로 이동 let downloadFrame = null;
// iframe에서 에피소드 처리
const processEpisodeInFrame = async (episodes, index, seriesName) => {
if (index >= episodes.length) {
log('모든 에피소드 다운로드 완료!');
if (downloadFrame) {
downloadFrame.remove();
downloadFrame = null;
}
return;
}
const episode = episodes[index];
log(`${index + 1}/${episodes.length} ${episode.title} 로딩 중...`);
// iframe 생성 (상단 1/3)
if (!downloadFrame) {
downloadFrame = document.createElement('iframe');
downloadFrame.style.position = 'fixed';
downloadFrame.style.top = '0';
downloadFrame.style.left = '0';
downloadFrame.style.width = '100%';
downloadFrame.style.height = '33.33vh';
downloadFrame.style.border = 'none';
downloadFrame.style.zIndex = '999999';
document.body.appendChild(downloadFrame);
}
// 에피소드 로드
downloadFrame.src = episode.url;
await new Promise((resolve) => {
downloadFrame.addEventListener('load', () => resolve(), { once: true });
});
log(`${episode.title} 로드 완료, 이미지 대기 중...`);
await sleep(7000);
// iframe에서 이미지 추출
let imageUrls = [];
try {
const iframeWin = downloadFrame.contentWindow;
const iframeDoc = iframeWin.document;
imageUrls = await getImagesWithLazyLoad(iframeDoc, iframeWin);
} catch (e) {
log('iframe 접근 오류, GM_xmlhttpRequest로 대체:', e);
// GM_xmlhttpRequest로 페이지 HTML 받아서 이미지 추출 시도
// (fallback - 추후 구현)
}
if (imageUrls.length > 0) {
await downloadImagesAsZip(imageUrls, seriesName, episode.title, episode.number);
} else {
log('이미지 없음 - 스킵');
}
await sleep(2000);
processEpisodeInFrame(episodes, index + 1, seriesName);
};
const startQueue = (episodes, seriesName) => { const startQueue = (episodes, seriesName) => {
log(`${episodes.length}개 에피소드를 큐에 저장, 첫 화로 이동합니다...`); log(`${episodes.length}개 에피소드 다운로드 시작 (iframe)...`);
sessionStorage.setItem(QUEUE_KEY, JSON.stringify(episodes)); processEpisodeInFrame(episodes, 0, seriesName);
sessionStorage.setItem(SERIES_KEY, seriesName);
sessionStorage.setItem(IDX_KEY, '0');
sessionStorage.setItem(LIST_KEY, window.location.href);
window.location.href = episodes[0].url;
}; };
async function downloadAllEpisodes() { async function downloadAllEpisodes() {
@@ -390,7 +443,6 @@
startQueue(episodes, seriesName); startQueue(episodes, seriesName);
} }
// n화 이후 다운로드
async function downloadAfterEpisode() { async function downloadAfterEpisode() {
const episodes = getEpisodesFromList(); const episodes = getEpisodesFromList();
if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; } if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; }
@@ -410,7 +462,6 @@
startQueue(filtered, seriesName); startQueue(filtered, seriesName);
} }
// 범위 다운로드
async function downloadRangeEpisodes() { async function downloadRangeEpisodes() {
const episodes = getEpisodesFromList(); const episodes = getEpisodesFromList();
if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; } if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; }
@@ -452,7 +503,7 @@
await sleep(7000); await sleep(7000);
try { try {
const imageUrls = await getImagesWithLazyLoad(); const imageUrls = await getImagesWithLazyLoad(document, window);
if (imageUrls.length > 0) { if (imageUrls.length > 0) {
const seriesName = sessionStorage.getItem(SERIES_KEY) || getSeriesName(); const seriesName = sessionStorage.getItem(SERIES_KEY) || getSeriesName();
const episodeTitle = getEpisodeTitle() || queue[idx].title; const episodeTitle = getEpisodeTitle() || queue[idx].title;