Auto-advance to the next Instagram Reel when the current one ends (handles looping) with a toggle button
// ==UserScript==
// @name IG Reels Auto-Advance (+ Toggle)
// @namespace https://github.com/wintrick/reels-auto-scroll
// @version 1.0
// @description Auto-advance to the next Instagram Reel when the current one ends (handles looping) with a toggle button
// @match https://www.instagram.com/*
// @run-at document-idle
// @grant none
// @license Apache 2.0
// ==/UserScript==
(function () {
'use strict';
var STORAGE_KEY = 'igReelsAutoScroll';
var enabled = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'true');
function createToggle() {
if (document.getElementById('ig-auto-advance-toggle')) return;
var btn = document.createElement('button');
btn.id = 'ig-auto-advance-toggle';
btn.textContent = 'Auto-Scroll: ' + (enabled ? 'ON' : 'OFF');
var s = btn.style;
s.position = 'fixed';
s.top = '80px';
s.right = '20px';
s.zIndex = '99999';
s.background = '#111';
s.color = '#fff';
s.border = 'none';
s.padding = '8px 12px';
s.borderRadius = '10px';
s.fontSize = '13px';
s.cursor = 'pointer';
s.opacity = '0.85';
btn.onclick = function () {
enabled = !enabled;
localStorage.setItem(STORAGE_KEY, JSON.stringify(enabled));
btn.textContent = 'Auto-Scroll: ' + (enabled ? 'ON' : 'OFF');
};
document.body.appendChild(btn);
}
function getScrollContainer() {
// Prefer a large, snap-scrolling container if present
var snap = document.querySelector('div[style*="scroll-snap-type"]');
if (snap && snap.scrollHeight > snap.clientHeight) return snap;
// Fallback: largest scrollable div roughly viewport-sized
var nodes = document.querySelectorAll('div');
var best = null;
var bestH = 0;
for (var i = 0; i < nodes.length; i++) {
var el = nodes[i];
var cs = window.getComputedStyle(el);
if ((cs.overflowY === 'auto' || cs.overflowY === 'scroll') &&
el.scrollHeight > el.clientHeight + 5 &&
el.clientHeight >= window.innerHeight * 0.6) {
if (el.clientHeight > bestH) { best = el; bestH = el.clientHeight; }
}
}
return best;
}
function smoothScrollNext() {
var sc = getScrollContainer();
var amount = (sc && sc.clientHeight) ? sc.clientHeight : window.innerHeight;
// Debounce global: avoid double-firing on the same near-end window
if (smoothScrollNext._lock) return;
smoothScrollNext._lock = true;
setTimeout(function(){ smoothScrollNext._lock = false; }, 800);
if (sc && typeof sc.scrollBy === 'function') {
sc.scrollBy({ top: amount, behavior: 'smooth' });
} else if (sc) {
sc.scrollTop = sc.scrollTop + amount;
} else {
window.scrollBy({ top: amount, behavior: 'smooth' });
}
}
function isInViewport(el) {
var r = el.getBoundingClientRect();
var vh = window.innerHeight || document.documentElement.clientHeight;
var vw = window.innerWidth || document.documentElement.clientWidth;
var visH = Math.min(r.bottom, vh) - Math.max(r.top, 0);
var visW = Math.min(r.right, vw) - Math.max(r.left, 0);
return visH > vh * 0.5 && visW > vw * 0.5;
}
function bindVideo(video) {
if (!video || video.dataset.igAutoBound === '1') return;
video.dataset.igAutoBound = '1';
// If reels ever stop looping, this still works:
video.addEventListener('ended', function () {
if (enabled) smoothScrollNext();
});
// Loop-safe near-end watcher
var tickTimer = null;
function checkNearEnd() {
if (!enabled) return;
if (!isInViewport(video)) return;
var d = video.duration, t = video.currentTime;
if (!isFinite(d) || d <= 0) return;
// Trigger within last ~200ms
if (d - t <= 0.2) {
if (video.dataset.igJustAdvanced !== '1') {
video.dataset.igJustAdvanced = '1';
smoothScrollNext();
setTimeout(function(){ video.dataset.igJustAdvanced = '0'; }, 1500);
}
}
}
function start() {
if (tickTimer) clearInterval(tickTimer);
tickTimer = setInterval(checkNearEnd, 200);
}
function stop() {
if (tickTimer) { clearInterval(tickTimer); tickTimer = null; }
}
video.addEventListener('play', start);
video.addEventListener('loadedmetadata', checkNearEnd);
video.addEventListener('timeupdate', checkNearEnd);
video.addEventListener('pause', stop);
// If it’s already playing when we attach:
if (!video.paused) start();
}
function scanAndBind() {
var vids = document.querySelectorAll('video');
for (var i = 0; i < vids.length; i++) bindVideo(vids[i]);
}
function init() {
createToggle();
scanAndBind();
// Watch for route changes / virtual DOM updates
var mo = new MutationObserver(function () {
createToggle();
scanAndBind();
});
mo.observe(document.documentElement, { childList: true, subtree: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();