탭 이동 → 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 + 스크롤
const triggerLazyLoading = async () => {
const viewer = document.querySelector('.vw-imgs, [class*="vw-imgs"]') || document;
// 우선 현재 img들 강제 로딩
const triggerLazyLoading = async (doc, win) => {
const d = doc || document;
const w = win || window;
const viewer = d.querySelector('.vw-imgs, [class*="vw-imgs"]') || d;
forceLoadImages(viewer);
// 동적 스크롤: 새 콘텐츠가 계속 로드되면 scrollHeight도 계속 증가
let prevHeight = 0;
let stuckCount = 0;
while (stuckCount < 3) {
const maxScroll = Math.max(
document.documentElement.scrollHeight,
document.body.scrollHeight, 0
d.documentElement.scrollHeight,
d.body.scrollHeight, 0
);
if (maxScroll === prevHeight) {
stuckCount++;
@@ -255,21 +255,18 @@
stuckCount = 0;
}
prevHeight = maxScroll;
// 현재 위치에서 끝까지 step 단위로 스크롤 (느리게)
const step = Math.min(300, maxScroll);
for (let s = window.scrollY; s <= maxScroll; s += step) {
window.scrollTo(0, Math.min(s, maxScroll));
for (let s = w.scrollY; s <= maxScroll; s += step) {
w.scrollTo(0, Math.min(s, maxScroll));
await sleep(300);
}
window.scrollTo(0, maxScroll);
// 스크롤 후 추가된 img들도 강제 로딩 (백그라운드 탭 IO 대체)
w.scrollTo(0, maxScroll);
forceLoadImages(viewer);
await sleep(1500);
}
// 끝까지 다 내려간 상태에서 여유 대기 + 최종 강제 로딩
forceLoadImages(viewer);
await sleep(2000);
window.scrollTo(0, 0);
w.scrollTo(0, 0);
await sleep(1000);
};
@@ -309,20 +306,22 @@
log(`저장: ${filename} (${imageUrls.length}개)`);
};
// 공통: 현재 페이지에서 이미지 획득 + lazy 로딩 트리거
const getImagesWithLazyLoad = async () => {
let urls = getEpisodeImages();
// 공통: 페이지/iframe에서 이미지 획득 + lazy 로딩 트리거
const getImagesWithLazyLoad = async (doc, win) => {
const d = doc || document;
const w = win || window;
let urls = getEpisodeImages(d);
if (urls.length === 0) {
for (let i = 0; i < 10; i++) {
await sleep(500);
urls = getEpisodeImages();
urls = getEpisodeImages(d);
if (urls.length > 0) break;
}
}
if (urls.length <= 3) {
log(`lazy loading 트리거 (${urls.length}개 → 전체)...`);
await triggerLazyLoading();
urls = getEpisodeImages();
await triggerLazyLoading(d, w);
urls = getEpisodeImages(d);
}
return urls;
};
@@ -330,7 +329,7 @@
async function downloadCurrentEpisode() {
try {
log('에피소드 다운로드 시작...');
const imageUrls = await getImagesWithLazyLoad();
const imageUrls = await getImagesWithLazyLoad(document, window);
if (imageUrls.length === 0) {
alert('이미지를 찾을 수 없습니다.');
@@ -372,14 +371,68 @@
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) => {
log(`${episodes.length}개 에피소드를 큐에 저장, 첫 화로 이동합니다...`);
sessionStorage.setItem(QUEUE_KEY, JSON.stringify(episodes));
sessionStorage.setItem(SERIES_KEY, seriesName);
sessionStorage.setItem(IDX_KEY, '0');
sessionStorage.setItem(LIST_KEY, window.location.href);
window.location.href = episodes[0].url;
log(`${episodes.length}개 에피소드 다운로드 시작 (iframe)...`);
processEpisodeInFrame(episodes, 0, seriesName);
};
async function downloadAllEpisodes() {
@@ -390,7 +443,6 @@
startQueue(episodes, seriesName);
}
// n화 이후 다운로드
async function downloadAfterEpisode() {
const episodes = getEpisodesFromList();
if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; }
@@ -410,7 +462,6 @@
startQueue(filtered, seriesName);
}
// 범위 다운로드
async function downloadRangeEpisodes() {
const episodes = getEpisodesFromList();
if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; }
@@ -452,7 +503,7 @@
await sleep(7000);
try {
const imageUrls = await getImagesWithLazyLoad();
const imageUrls = await getImagesWithLazyLoad(document, window);
if (imageUrls.length > 0) {
const seriesName = sessionStorage.getItem(SERIES_KEY) || getSeriesName();
const episodeTitle = getEpisodeTitle() || queue[idx].title;