Skip to content

Instantly share code, notes, and snippets.

@nikspyratos
Created August 1, 2025 10:40
Show Gist options
  • Save nikspyratos/3c5b82d24e835d63419700d26db0e696 to your computer and use it in GitHub Desktop.
Save nikspyratos/3c5b82d24e835d63419700d26db0e696 to your computer and use it in GitHub Desktop.
Instagram anti-doomscroll tampermonkey script (Claude vibes)
// ==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);
})();
@nikspyratos
Copy link
Author

nikspyratos commented Aug 1, 2025

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

  • Home & explore pages prevent scrolling
  • Reels navigation is hidden
  • Entering the reels URL in your browser will direct you to the home page
  • Opening a /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 friends

Usage

  1. Install Tampermonkey
  2. Create a new script, paste this code in

Issues

  • The messages sub-window that opens when navigating from messages to explore will infinitely scroll upwards
  • Swiping on home page carousels seems to break the image display on that carousel

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment