Created
August 1, 2025 10:40
-
-
Save nikspyratos/3c5b82d24e835d63419700d26db0e696 to your computer and use it in GitHub Desktop.
Instagram anti-doomscroll tampermonkey script (Claude vibes)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Instagram Anti-Doomscroll | |
// @namespace http://tampermonkey.net/ | |
// @version 2.0 | |
// @description Block Instagram reels browsing and prevent scrolling on home/explore pages | |
// @author You | |
// @match https://www.instagram.com/* | |
// @match https://instagram.com/* | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// Function to check if current URL contains "reels" but not "reel" (singular) | |
function shouldRedirect() { | |
const url = window.location.href; | |
// Check if URL contains "reels" (plural) but not just "reel" (singular) | |
// This allows individual reel URLs like /reel/ABC123 but blocks /reels/ | |
return url.includes('/reels') && !url.match(/\/reel\/[^\/]+/); | |
} | |
// Function to check if we're on a page that should have scroll blocking | |
function isScrollBlockedPage() { | |
return isScrollBlockedPageForUrl(window.location.href); | |
} | |
// Helper function to check scroll blocking for any URL | |
function isScrollBlockedPageForUrl(url) { | |
if (!url) return false; | |
try { | |
const urlObj = new URL(url); | |
const path = urlObj.pathname; | |
// Home feed (root path or just instagram.com) | |
if (path === '/' || path === '') { | |
return true; | |
} | |
// Explore page (but not explore/tags or other sub-pages) | |
if (path === '/explore' || path === '/explore/') { | |
return true; | |
} | |
// Explicitly exclude common pages that should allow scrolling | |
const allowedPaths = [ | |
'/direct/', // Direct messages | |
'/accounts/', // Account settings | |
'/p/', // Individual posts | |
'/reel/', // Individual reels | |
'/tv/', // IGTV | |
'/stories/', // Stories | |
'/reels/', // This will be redirected anyway | |
'/explore/tags/', // Hashtag pages | |
'/explore/locations/' // Location pages | |
]; | |
// Check if current path starts with any allowed path | |
for (const allowedPath of allowedPaths) { | |
if (path.startsWith(allowedPath)) { | |
return false; | |
} | |
} | |
// If it's a user profile (starts with username), allow scrolling | |
// Profile URLs are typically just /username/ or /username/posts/ etc. | |
const pathParts = path.split('/').filter(part => part.length > 0); | |
if (pathParts.length >= 1 && !pathParts[0].includes('.') && pathParts[0] !== 'explore') { | |
// This looks like a username path, allow scrolling | |
return false; | |
} | |
return false; | |
} catch (e) { | |
// If URL parsing fails, be conservative and don't block | |
console.log('Instagram Anti-Doomscroll: URL parsing failed, not blocking scroll'); | |
return false; | |
} | |
} | |
// Function to redirect to Google | |
function redirectToGoogle() { | |
console.log('Instagram Anti-Doomscroll: Redirecting away from reels feed'); | |
window.location.href = 'https://www.google.com'; | |
} | |
// Function to redirect reels URL to reel URL | |
function redirectReelsToReel() { | |
const currentUrl = window.location.href; | |
console.log('Instagram Anti-Doomscroll: Converting reels URL to reel URL'); | |
// Try to extract reel ID from various reels URL formats | |
// Examples: | |
// /reels/ABC123/ -> /reel/ABC123/ | |
// /reels/ABC123/?utm_source=... -> /reel/ABC123/?utm_source=... | |
let reelUrl = currentUrl; | |
// Replace /reels/ with /reel/ in the path | |
reelUrl = reelUrl.replace('/reels/', '/reel/'); | |
// If it's just /reels without a specific reel ID, redirect to home instead | |
if (reelUrl.endsWith('/reel/') || reelUrl.includes('/reel/?') || reelUrl.includes('/reel/#')) { | |
console.log('Instagram Anti-Doomscroll: No specific reel ID found, redirecting to home'); | |
window.location.href = window.location.origin + '/'; | |
} else { | |
console.log('Instagram Anti-Doomscroll: Redirecting to:', reelUrl); | |
window.location.href = reelUrl; | |
} | |
} | |
// Function to hide the Reels link in sidebar | |
function hideReelsLink() { | |
// Only target the main navigation reels link, not every possible reels link | |
const selectors = [ | |
'a[href="/reels/"]', | |
'[data-testid="reels-tab"]', | |
'nav a[href="/reels/"]' | |
]; | |
selectors.forEach(selector => { | |
const elements = document.querySelectorAll(selector); | |
elements.forEach(element => { | |
// Only hide if not already hidden to prevent layout thrashing | |
let container = element.closest('div[role="menuitem"]') || | |
element.closest('li') || | |
element.closest('div'); | |
if (container && container.style.display !== 'none') { | |
container.style.display = 'none'; | |
console.log('Instagram Anti-Doomscroll: Hidden main reels navigation link'); | |
} | |
}); | |
}); | |
} | |
// Enhanced function to block reels navigation attempts | |
function blockReelsNavigation() { | |
// Override click events on reels links | |
document.addEventListener('click', function(e) { | |
const target = e.target.closest('a'); | |
if (target && target.href && target.href.includes('/reels')) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
console.log('Instagram Anti-Doomscroll: Blocked reels navigation attempt'); | |
showBlockedMessage(); | |
} | |
}, true); | |
// Monitor for URL changes that might indicate programmatic navigation to reels | |
let lastUrl = window.location.href; | |
const urlCheckInterval = setInterval(() => { | |
const currentUrl = window.location.href; | |
if (currentUrl !== lastUrl) { | |
if (shouldRedirect()) { | |
console.log('Instagram Anti-Doomscroll: Detected navigation to reels, redirecting'); | |
redirectReelsToReel(); | |
return; | |
} | |
lastUrl = currentUrl; | |
} | |
}, 100); | |
// Store interval for cleanup | |
blockReelsNavigation.urlCheckInterval = urlCheckInterval; | |
} | |
// Function to show blocked message | |
function showBlockedMessage() { | |
const message = document.createElement('div'); | |
message.textContent = 'Reels browsing blocked - staying focused!'; | |
message.style.cssText = ` | |
position: fixed; | |
top: 20px; | |
right: 20px; | |
background: #ff4444; | |
color: white; | |
padding: 10px; | |
border-radius: 5px; | |
z-index: 9999; | |
font-size: 14px; | |
`; | |
document.body.appendChild(message); | |
setTimeout(() => { | |
if (message.parentNode) { | |
message.remove(); | |
} | |
}, 3000); | |
} | |
// Function to completely block scrolling on home/explore pages | |
function initScrollBlocking() { | |
if (!isScrollBlockedPage()) { | |
return; | |
} | |
console.log('Instagram Anti-Doomscroll: Blocking all scrolling on this page'); | |
// Show message explaining scroll blocking | |
showScrollBlockedMessage(); | |
// Block all scroll events using event capturing | |
document.addEventListener('scroll', blockScrolling, { capture: true, passive: false }); | |
document.addEventListener('wheel', blockScrolling, { capture: true, passive: false }); | |
document.addEventListener('touchstart', blockScrolling, { capture: true, passive: false }); | |
document.addEventListener('touchmove', blockScrolling, { capture: true, passive: false }); | |
document.addEventListener('keydown', blockKeyScroll, { capture: true, passive: false }); | |
// Block programmatic scrolling (safer approach) | |
let scrollToBlocked = false; | |
let scrollByBlocked = false; | |
// Try to override if possible, but don't fail if read-only | |
try { | |
const originalScrollTo = window.scrollTo; | |
window.scrollTo = function() { | |
if (isScrollBlockedPage()) { | |
console.log('Instagram Anti-Doomscroll: Blocked programmatic scrollTo'); | |
return; | |
} | |
originalScrollTo.apply(window, arguments); | |
}; | |
scrollToBlocked = true; | |
} catch (e) { | |
console.log('Instagram Anti-Doomscroll: Could not override scrollTo (read-only)'); | |
} | |
try { | |
const originalScrollBy = window.scrollBy; | |
window.scrollBy = function() { | |
if (isScrollBlockedPage()) { | |
console.log('Instagram Anti-Doomscroll: Blocked programmatic scrollBy'); | |
return; | |
} | |
originalScrollBy.apply(window, arguments); | |
}; | |
scrollByBlocked = true; | |
} catch (e) { | |
console.log('Instagram Anti-Doomscroll: Could not override scrollBy (read-only)'); | |
} | |
// Override scrollTop and scrollLeft on all elements - THIS IS THE CULPRIT | |
// Store the interval so we can clear it later | |
if (initScrollBlocking.scrollResetInterval) { | |
clearInterval(initScrollBlocking.scrollResetInterval); | |
} | |
initScrollBlocking.scrollResetInterval = setInterval(() => { | |
if (!isScrollBlockedPage()) { | |
// If we're no longer on a scroll-blocked page, stop this interval | |
clearInterval(initScrollBlocking.scrollResetInterval); | |
initScrollBlocking.scrollResetInterval = null; | |
return; | |
} | |
const allElements = document.querySelectorAll('*'); | |
allElements.forEach(element => { | |
if (element.scrollTop > 0) { | |
element.scrollTop = 0; | |
} | |
if (element.scrollLeft > 0) { | |
element.scrollLeft = 0; | |
} | |
}); | |
}, 100); | |
} | |
function blockScrolling(e) { | |
if (!isScrollBlockedPage()) return; | |
e.preventDefault(); | |
e.stopPropagation(); | |
e.stopImmediatePropagation(); | |
return false; | |
} | |
function blockKeyScroll(e) { | |
if (!isScrollBlockedPage()) return; | |
// Block scroll-related keys: arrow keys, page up/down, space, home, end | |
const scrollKeys = [32, 33, 34, 35, 36, 37, 38, 39, 40]; | |
if (scrollKeys.includes(e.keyCode)) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
e.stopImmediatePropagation(); | |
return false; | |
} | |
} | |
function showScrollBlockedMessage() { | |
// Remove any existing message first | |
const existingMessage = document.getElementById('instagram-scroll-blocked-message'); | |
if (existingMessage) { | |
existingMessage.remove(); | |
} | |
const message = document.createElement('div'); | |
message.id = 'instagram-scroll-blocked-message'; | |
message.innerHTML = ` | |
<div style="font-weight: bold; margin-bottom: 5px;">Scrolling disabled on this page</div> | |
<div style="font-size: 12px;">Navigate to specific posts or profiles to browse content.</div> | |
<div style="font-size: 12px; margin-top: 5px;">This prevents endless scrolling and helps you stay focused.</div> | |
`; | |
message.style.cssText = ` | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
background: #ff6b35; | |
color: white; | |
padding: 20px; | |
border-radius: 10px; | |
z-index: 10000; | |
font-size: 14px; | |
text-align: center; | |
box-shadow: 0 4px 20px rgba(0,0,0,0.3); | |
max-width: 350px; | |
transition: opacity 0.3s ease; | |
`; | |
document.body.appendChild(message); | |
// Auto-hide after 8 seconds, but only if still on a scroll-blocked page | |
setTimeout(() => { | |
if (message.parentNode && isScrollBlockedPage()) { | |
message.style.opacity = '0'; | |
setTimeout(() => { | |
if (message.parentNode) { | |
message.remove(); | |
} | |
}, 300); | |
} | |
}, 8000); | |
} | |
function hideScrollBlockedMessage() { | |
const message = document.getElementById('instagram-scroll-blocked-message'); | |
if (message) { | |
message.style.opacity = '0'; | |
setTimeout(() => { | |
if (message.parentNode) { | |
message.remove(); | |
} | |
}, 300); | |
} | |
} | |
function removeScrollBlockingListeners() { | |
document.removeEventListener('scroll', blockScrolling, { capture: true }); | |
document.removeEventListener('wheel', blockScrolling, { capture: true }); | |
document.removeEventListener('touchstart', blockScrolling, { capture: true }); | |
document.removeEventListener('touchmove', blockScrolling, { capture: true }); | |
document.removeEventListener('keydown', blockKeyScroll, { capture: true }); | |
// Clean up the scroll reset interval - THIS WAS MISSING! | |
if (initScrollBlocking.scrollResetInterval) { | |
clearInterval(initScrollBlocking.scrollResetInterval); | |
initScrollBlocking.scrollResetInterval = null; | |
console.log('Instagram Anti-Doomscroll: Cleared scroll reset interval'); | |
} | |
// Clean up reels navigation URL checking interval | |
if (blockReelsNavigation.urlCheckInterval) { | |
clearInterval(blockReelsNavigation.urlCheckInterval); | |
blockReelsNavigation.urlCheckInterval = null; | |
} | |
console.log('Instagram Anti-Doomscroll: Removed scroll blocking listeners'); | |
} | |
// Main execution | |
function init() { | |
// Check if we should redirect immediately | |
if (shouldRedirect()) { | |
redirectReelsToReel(); | |
return; | |
} | |
// Hide reels links | |
hideReelsLink(); | |
// Block reels navigation | |
blockReelsNavigation(); | |
// Initialize scroll blocking | |
initScrollBlocking(); | |
// Set up observer for dynamically loaded content | |
const observer = new MutationObserver(function(mutations) { | |
let shouldHide = false; | |
mutations.forEach(function(mutation) { | |
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { | |
shouldHide = true; | |
} | |
}); | |
if (shouldHide) { | |
hideReelsLink(); | |
} | |
}); | |
// Start observing | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true | |
}); | |
// Enhanced SPA URL change detection | |
let currentUrl = window.location.href; | |
// Method 1: Override History API methods | |
const originalPushState = history.pushState; | |
const originalReplaceState = history.replaceState; | |
history.pushState = function(...args) { | |
originalPushState.apply(history, args); | |
setTimeout(() => { | |
handleUrlChange(); | |
}, 0); | |
}; | |
history.replaceState = function(...args) { | |
originalReplaceState.apply(history, args); | |
setTimeout(() => { | |
handleUrlChange(); | |
}, 0); | |
}; | |
// Method 2: Listen for popstate events | |
window.addEventListener('popstate', handleUrlChange); | |
// Method 3: Polling fallback | |
setInterval(() => { | |
if (window.location.href !== currentUrl) { | |
handleUrlChange(); | |
} | |
}, 250); | |
// Method 4: Listen for hashchange events | |
window.addEventListener('hashchange', handleUrlChange); | |
// Unified URL change handler | |
function handleUrlChange() { | |
const newUrl = window.location.href; | |
if (newUrl !== currentUrl) { | |
const oldUrl = currentUrl; | |
currentUrl = newUrl; | |
console.log('Instagram Anti-Doomscroll: URL changed from', oldUrl, 'to', currentUrl); | |
// Check page types for old and new URLs | |
const wasScrollBlockedPage = isScrollBlockedPageForUrl(oldUrl); | |
const isNowScrollBlockedPage = isScrollBlockedPageForUrl(newUrl); | |
console.log('Instagram Anti-Doomscroll: Was scroll blocked:', wasScrollBlockedPage, 'Is now scroll blocked:', isNowScrollBlockedPage); | |
// Remove old scroll blocking listeners | |
removeScrollBlockingListeners(); | |
if (shouldRedirect()) { | |
redirectReelsToReel(); | |
return; | |
} | |
// Handle message visibility based on page type change | |
if (isNowScrollBlockedPage && !wasScrollBlockedPage) { | |
// Moving TO a scroll-blocked page - show message and init blocking | |
console.log('Instagram Anti-Doomscroll: Moving to scroll-blocked page'); | |
setTimeout(initScrollBlocking, 100); | |
} else if (!isNowScrollBlockedPage && wasScrollBlockedPage) { | |
// Moving AWAY from a scroll-blocked page - hide message | |
console.log('Instagram Anti-Doomscroll: Moving away from scroll-blocked page'); | |
hideScrollBlockedMessage(); | |
} else if (isNowScrollBlockedPage && wasScrollBlockedPage) { | |
// Staying within scroll-blocked pages - reinit blocking | |
console.log('Instagram Anti-Doomscroll: Staying within scroll-blocked pages'); | |
setTimeout(initScrollBlocking, 100); | |
} | |
// If both are false, do nothing (staying on unrestricted pages) | |
// Re-hide reels links after navigation with multiple delays | |
setTimeout(hideReelsLink, 100); | |
setTimeout(hideReelsLink, 500); | |
setTimeout(hideReelsLink, 1000); | |
setTimeout(hideReelsLink, 2000); | |
} | |
} | |
} | |
// Wait for page to load | |
if (document.readyState === 'loading') { | |
document.addEventListener('DOMContentLoaded', init); | |
} else { | |
init(); | |
} | |
// Also run after a short delay to catch late-loading elements | |
setTimeout(init, 2000); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Made with Claude.
I want to keep up with my friends, artists, etc, but want to not get sucked into scrolling.
Hard blocking the site just makes me disable the blockers. This keeps me on track with visual reminders and preventing navigating to the specific addictive parts of the site.
Features
/reels/{id}
url will redirect to/reel/{id}
-reels
opens the reel within the doomscroll feed, so this bypasses that while still letting you open links from friendsUsage
Issues
Notes
Vibe coded one for X/Twitter too: https://gist.github.com/nikspyratos/a340af08e1a72acf76ce981f0900554c
For Youtube on web, I recommend Unhook.
For Android, I recommend ScreenZen - it can block specifically the short form video sections of Instagram and Youtube.