Limit Instagram posts, reels, and comments and reveal more only when you choose.
// ==UserScript==
// @name IG Scroll Brake
// @namespace https://github.com/atharvj/ig-scroll-brake
// @version 1.1.1
// @description Limit Instagram posts, reels, and comments and reveal more only when you choose.
// @author Intellectual07
// @license MIT
// @match https://www.instagram.com/*
// @match https://instagram.com/*
// @match https://m.instagram.com/*
// @run-at document-idle
// @grant none
// @noframes
// ==/UserScript==
(function () {
"use strict";
const INITIAL_VISIBLE_ITEMS = 10;
const INITIAL_VISIBLE_REELS = 5;
const ITEMS_PER_CLICK = 5;
const BRAKE_ID = "ig-scroll-brake";
const STYLE_ID = "ig-scroll-brake-styles";
const STORAGE_KEY = "ig-scroll-brake-items-per-click";
const SCAN_DELAY_MS = 75;
const ROUTE_SETTLE_DELAY_MS = 300;
const EXPAND_LOCK_GRACE_MS = 1200;
const BRAKE_PANEL_HEIGHT = 104;
const TOP_RESET_SCROLL_Y = 120;
const TOP_RESET_MIN_DISTANCE = 600;
const MIN_ITEMS_PER_CLICK = 1;
const MAX_ITEMS_PER_CLICK = 100;
const MAX_SAVED_ROUTE_STATES = 12;
const MIN_MEDIA_WIDTH = 80;
const MIN_MEDIA_HEIGHT = 48;
const MIN_COMMENT_WIDTH = 140;
const MIN_COMMENT_HEIGHT = 20;
const MEDIA_PATH_RE = /^\/(?:[^/]+\/)?(p|reel|tv)\/([^/?#]+)/i;
const COMMENT_PATH_RE = /^\/(?:[^/]+\/)?(p|reel|tv)\/([^/?#]+)\/c\/([^/?#]+)/i;
const PROFILE_PATH_RE = /^\/([A-Za-z0-9._]{1,30})(?:\/(?:reels|tagged))?\/?$/;
const INACTIVE_ROUTE_RE = /^\/(?:about|accounts|ads|api|challenge|developer|direct|emails|legal|oauth|privacy|settings|stories|terms|web)(?:\/|$)/i;
const RESERVED_PROFILE_SEGMENTS = new Set([
"about",
"accounts",
"ads",
"api",
"challenge",
"developer",
"direct",
"emails",
"explore",
"legal",
"oauth",
"p",
"privacy",
"reel",
"reels",
"settings",
"stories",
"terms",
"tv",
"web",
]);
let visibleItemLimit = getInitialVisibleItemLimit();
let itemsPerClick = readSavedItemsPerClick();
let currentRouteKey = getRouteKey();
let scanTimer = null;
let routeSettlesAt = 0;
let lastTouchY = null;
let isLocked = false;
let maxScrollPosition = null;
let lockScroller = null;
let lastScrollPosition = getScrollPosition(window);
let minPositionForNextLock = null;
let allowMaxIncreaseUntil = 0;
let activeReelKey = "";
let lastObservedHref = location.href;
const orderedKeys = [];
const routeStates = new Map();
function addStyles() {
if (document.getElementById(STYLE_ID)) {
return;
}
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = `
#${BRAKE_ID} {
--isb-accent: #0095f6;
--isb-accent-hover: #1877f2;
--isb-background: #ffffff;
--isb-border: #dbdbdb;
--isb-muted: #737373;
--isb-text: #262626;
position: fixed;
left: 50%;
bottom: max(18px, env(safe-area-inset-bottom));
z-index: 2147483647;
box-sizing: border-box;
width: min(540px, calc(100vw - 28px));
overflow: hidden;
transform: translateX(-50%);
border: 1px solid var(--isb-border);
border-radius: 16px;
background: var(--isb-background);
color: var(--isb-text);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color-scheme: light dark;
}
#${BRAKE_ID}::before {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 3px;
background: linear-gradient(90deg, #feda75, #fa7e1e 24%, #d62976 52%, #962fbf 76%, #4f5bd5);
content: "";
}
#${BRAKE_ID}[hidden] {
display: none !important;
}
#${BRAKE_ID} .isb-inner {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 8px 10px;
align-items: center;
padding: 13px 14px 11px;
}
#${BRAKE_ID} .isb-mark {
display: grid;
width: 34px;
height: 34px;
grid-row: 1 / span 2;
place-items: center;
border-radius: 10px;
background: linear-gradient(145deg, #feda75, #fa7e1e 25%, #d62976 52%, #962fbf 76%, #4f5bd5);
color: #ffffff;
box-shadow: 0 2px 8px rgba(214, 41, 118, 0.24);
}
#${BRAKE_ID} .isb-mark svg {
display: block;
width: 21px;
height: 21px;
}
#${BRAKE_ID} .isb-title {
overflow: hidden;
font-size: 14px;
font-weight: 700;
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
#${BRAKE_ID} .isb-status {
overflow: hidden;
color: var(--isb-muted);
font-size: 12px;
font-weight: 400;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
#${BRAKE_ID} .isb-controls {
display: flex;
grid-column: 1 / -1;
align-items: center;
justify-content: center;
gap: 8px;
}
#${BRAKE_ID} button {
min-height: 36px;
flex: 1 1 auto;
border: 0;
border-radius: 9px;
padding: 8px 14px;
background: var(--isb-accent);
color: #ffffff;
font: 600 14px/18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
cursor: pointer;
}
#${BRAKE_ID} button:hover {
background: var(--isb-accent-hover);
}
#${BRAKE_ID} button:active {
opacity: 0.78;
}
#${BRAKE_ID} button:focus-visible,
#${BRAKE_ID} input:focus-visible {
outline: 3px solid rgba(0, 149, 246, 0.3);
outline-offset: 2px;
}
#${BRAKE_ID} label {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: 6px;
color: var(--isb-muted);
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
#${BRAKE_ID} input {
box-sizing: border-box;
width: 58px;
height: 36px;
border: 1px solid var(--isb-border);
border-radius: 9px;
padding: 7px 6px;
background: var(--isb-background);
color: var(--isb-text);
font: 600 14px/18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
text-align: center;
}
@media (max-width: 430px) {
#${BRAKE_ID} {
bottom: max(10px, env(safe-area-inset-bottom));
width: calc(100vw - 20px);
}
#${BRAKE_ID} .isb-inner {
padding: 12px 12px 10px;
}
#${BRAKE_ID} label span {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
clip-path: inset(50%);
}
}
@media (prefers-color-scheme: dark) {
#${BRAKE_ID} {
--isb-background: #000000;
--isb-border: #363636;
--isb-muted: #a8a8a8;
--isb-text: #f5f5f5;
box-shadow: 0 12px 42px rgba(0, 0, 0, 0.62);
}
}
@media (prefers-reduced-motion: reduce) {
#${BRAKE_ID},
#${BRAKE_ID} * {
scroll-behavior: auto !important;
transition: none !important;
}
}
`;
(document.head || document.documentElement).appendChild(style);
}
function clampWholeNumber(value, fallback, min, max) {
const number = Number(value);
if (!Number.isFinite(number)) {
return fallback;
}
return Math.min(max, Math.max(min, Math.floor(number)));
}
function getInitialVisibleItemLimit() {
const initialLimit = getSurface().kind === "reels"
? INITIAL_VISIBLE_REELS
: INITIAL_VISIBLE_ITEMS;
return clampWholeNumber(initialLimit, 10, 1, Number.MAX_SAFE_INTEGER);
}
function normalizedPathname() {
const pathname = location.pathname.replace(/\/{2,}/g, "/");
return pathname === "/" ? pathname : pathname.replace(/\/$/, "");
}
function isContinuousReelsRoute() {
return /^\/reels?(?:\/|$)/i.test(normalizedPathname());
}
function getRouteKey() {
if (isContinuousReelsRoute()) {
return "/reels/:continuous";
}
const pathname = normalizedPathname();
if (pathname === "/") {
const variant = new URLSearchParams(location.search).get("variant");
return variant ? `/?variant=${encodeURIComponent(variant)}` : "/";
}
return pathname;
}
function readSavedItemsPerClick() {
let savedValue = null;
try {
savedValue = window.localStorage.getItem(STORAGE_KEY);
} catch {
savedValue = null;
}
return clampWholeNumber(
savedValue || ITEMS_PER_CLICK,
ITEMS_PER_CLICK,
MIN_ITEMS_PER_CLICK,
MAX_ITEMS_PER_CLICK
);
}
function saveItemsPerClick() {
try {
window.localStorage.setItem(STORAGE_KEY, String(itemsPerClick));
} catch {
// The control still works for the current page when storage is unavailable.
}
}
function getSurface() {
const pathname = normalizedPathname();
if (INACTIVE_ROUTE_RE.test(pathname)) {
return { active: false, kind: "inactive", singular: "item", plural: "items" };
}
if (/^\/(?:p|tv)\/[^/]+/i.test(pathname)) {
return { active: true, kind: "comments", singular: "comment", plural: "comments" };
}
if (isContinuousReelsRoute()) {
return { active: true, kind: "reels", singular: "reel", plural: "reels" };
}
return { active: true, kind: "media", singular: "post", plural: "posts" };
}
function getMainContent() {
return (
document.querySelector('main[role="main"]') ||
document.querySelector("main") ||
document.querySelector('[role="main"]') ||
document.body
);
}
function parseUrl(value) {
try {
return new URL(value, location.origin);
} catch {
return null;
}
}
function getMediaInfoFromHref(href) {
const url = parseUrl(href);
if (!url || !/(^|\.)instagram\.com$/i.test(url.hostname)) {
return null;
}
if (COMMENT_PATH_RE.test(url.pathname)) {
return null;
}
const match = url.pathname.match(MEDIA_PATH_RE);
if (!match) {
return null;
}
return {
kind: match[1].toLowerCase(),
code: decodeURIComponent(match[2]),
key: `media:${decodeURIComponent(match[2])}`,
};
}
function getCommentInfoFromHref(href) {
const url = parseUrl(href);
if (!url || !/(^|\.)instagram\.com$/i.test(url.hostname)) {
return null;
}
const pathMatch = url.pathname.match(COMMENT_PATH_RE);
if (pathMatch) {
return {
mediaCode: decodeURIComponent(pathMatch[2]),
commentId: decodeURIComponent(pathMatch[3]),
key: `comment:${decodeURIComponent(pathMatch[2])}:${decodeURIComponent(pathMatch[3])}`,
};
}
const mediaMatch = url.pathname.match(MEDIA_PATH_RE);
const commentId = url.searchParams.get("comment_id");
if (!mediaMatch || !commentId) {
return null;
}
return {
mediaCode: decodeURIComponent(mediaMatch[2]),
commentId,
key: `comment:${decodeURIComponent(mediaMatch[2])}:${commentId}`,
};
}
function getAttributeValue(element, names) {
if (!(element instanceof Element)) {
return "";
}
for (const name of names) {
const value = element.getAttribute(name);
if (value) {
return value;
}
}
return "";
}
function hashText(value) {
let hash = 2166136261;
for (let index = 0; index < value.length; index += 1) {
hash ^= value.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return (hash >>> 0).toString(36);
}
function normalizeText(value) {
return String(value || "").replace(/\s+/g, " ").trim();
}
function getElementMediaKey(element) {
if (!(element instanceof Element)) {
return "";
}
const directId = getAttributeValue(element, [
"data-media-id",
"data-media-pk",
"data-shortcode",
"data-code",
]);
if (directId) {
return `media:${directId}`;
}
const links = element.matches("a[href]")
? [element]
: Array.from(element.querySelectorAll("a[href]"));
const timedLink = links.find((link) => link.querySelector("time") && getMediaInfoFromHref(link.getAttribute("href")));
const mediaLink = timedLink || links.find((link) => getMediaInfoFromHref(link.getAttribute("href")));
if (mediaLink) {
return getMediaInfoFromHref(mediaLink.getAttribute("href")).key;
}
const video = element.matches("video") ? element : element.querySelector("video");
if (!video) {
return "";
}
const author = element.querySelector('a[href^="/"]:not([href^="/p/"]):not([href^="/reel/"])');
const poster = video.getAttribute("poster") || video.currentSrc || video.getAttribute("src") || "";
const posterUrl = parseUrl(poster);
const stablePoster = posterUrl ? posterUrl.pathname : poster;
const fingerprint = normalizeText(
`${author ? author.getAttribute("href") : ""}|${stablePoster}|${element.getAttribute("aria-label") || ""}`
);
if (fingerprint.replace(/[|]/g, "")) {
return `media-fallback:${hashText(fingerprint)}`;
}
const currentMedia = getMediaInfoFromHref(location.href);
return currentMedia ? currentMedia.key : "";
}
function getDistinctMediaKeys(element) {
const keys = new Set();
if (!(element instanceof Element)) {
return keys;
}
if (element.matches("a[href]")) {
const info = getMediaInfoFromHref(element.getAttribute("href"));
if (info) {
keys.add(info.key);
}
}
element.querySelectorAll("a[href]").forEach((link) => {
const info = getMediaInfoFromHref(link.getAttribute("href"));
if (info) {
keys.add(info.key);
}
});
return keys;
}
function isExcludedElement(element) {
return Boolean(
element.closest(
`#${BRAKE_ID}, aside, nav, header, footer, dialog, [aria-modal="true"], [role="dialog"], [role="navigation"], [role="complementary"]`
)
);
}
function isCountableElement(element, minWidth, minHeight) {
if (!(element instanceof Element) || isExcludedElement(element)) {
return false;
}
if (element.closest('[hidden], [aria-hidden="true"]')) {
return false;
}
const rect = element.getBoundingClientRect();
if (rect.width < minWidth || rect.height < minHeight) {
return false;
}
const style = window.getComputedStyle(element);
return style.display !== "none" && style.visibility !== "hidden" && Number(style.opacity || 1) !== 0;
}
function findMediaContainerFromLink(link, root) {
const article = link.closest("article");
if (article && root.contains(article)) {
return article;
}
let current = link;
let best = link;
let depth = 0;
while (current && current !== root && current !== document.body && depth < 7) {
const keys = getDistinctMediaKeys(current);
if (keys.size > 1) {
break;
}
if (keys.size === 1) {
const rect = current.getBoundingClientRect();
if (rect.width >= MIN_MEDIA_WIDTH && rect.height >= MIN_MEDIA_HEIGHT) {
best = current;
}
}
current = current.parentElement;
depth += 1;
}
return best;
}
function findMediaContainerFromVideo(video, root) {
const article = video.closest("article");
if (article && root.contains(article)) {
return article;
}
let current = video;
let best = video;
let depth = 0;
while (current && current !== root && current !== document.body && depth < 8) {
const videos = current.matches("video") ? 1 : current.querySelectorAll("video").length;
if (videos > 1) {
break;
}
const rect = current.getBoundingClientRect();
if (rect.width >= MIN_MEDIA_WIDTH && rect.height >= MIN_MEDIA_HEIGHT) {
best = current;
}
current = current.parentElement;
depth += 1;
}
return best;
}
function sortRecordsInDocumentOrder(records) {
return records.sort((left, right) => {
if (left.item === right.item) {
return 0;
}
const position = left.item.compareDocumentPosition(right.item);
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
return -1;
}
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
return 1;
}
return 0;
});
}
function getMediaRecords() {
const root = getMainContent();
if (!root) {
return [];
}
const seenKeys = new Set();
const records = [];
root.querySelectorAll("article").forEach((article) => {
if (article.parentElement && article.parentElement.closest("article")) {
return;
}
const key = getElementMediaKey(article);
if (!key || seenKeys.has(key) || !isCountableElement(article, MIN_MEDIA_WIDTH, MIN_MEDIA_HEIGHT)) {
return;
}
seenKeys.add(key);
records.push({ item: article, key });
});
root.querySelectorAll('a[href*="/p/"], a[href*="/reel/"], a[href*="/tv/"]').forEach((link) => {
const info = getMediaInfoFromHref(link.getAttribute("href"));
if (!info || seenKeys.has(info.key)) {
return;
}
const item = findMediaContainerFromLink(link, root);
if (!isCountableElement(item, MIN_MEDIA_WIDTH, MIN_MEDIA_HEIGHT)) {
return;
}
seenKeys.add(info.key);
records.push({ item, key: info.key });
});
root.querySelectorAll("video").forEach((video) => {
const item = findMediaContainerFromVideo(video, root);
const key = getElementMediaKey(item);
if (!key || seenKeys.has(key) || !isCountableElement(item, MIN_MEDIA_WIDTH, MIN_MEDIA_HEIGHT)) {
return;
}
seenKeys.add(key);
records.push({ item, key });
});
return sortRecordsInDocumentOrder(records);
}
function getCommentKey(comment) {
const commentId = getAttributeValue(comment, [
"data-comment-id",
"data-comment-pk",
"data-id",
]);
const currentMedia = getMediaInfoFromHref(location.href);
if (commentId) {
return `comment:${currentMedia ? currentMedia.code : "unknown"}:${commentId}`;
}
const links = comment.matches("a[href]")
? [comment]
: Array.from(comment.querySelectorAll("a[href]"));
const commentLink = links.find((link) => getCommentInfoFromHref(link.getAttribute("href")));
if (commentLink) {
return getCommentInfoFromHref(commentLink.getAttribute("href")).key;
}
const time = comment.querySelector("time[datetime]");
const author = Array.from(comment.querySelectorAll('a[href^="/"]')).find((link) => {
const href = link.getAttribute("href") || "";
const match = href.match(PROFILE_PATH_RE);
return match && !RESERVED_PROFILE_SEGMENTS.has(match[1].toLowerCase());
});
const text = normalizeText(comment.textContent);
const timeLink = time ? time.closest("a[href]") : null;
if (
!time ||
!author ||
text.length < 2 ||
(timeLink && getMediaInfoFromHref(timeLink.getAttribute("href")))
) {
return "";
}
const fingerprint = `${currentMedia ? currentMedia.code : "unknown"}|${author.getAttribute("href")}|${time.getAttribute("datetime")}|${text}`;
return `comment-fallback:${hashText(fingerprint)}`;
}
function getCommentContainer(element, root) {
return (
element.closest("[data-comment-id], [data-comment-pk]") ||
element.closest("li") ||
element.closest('[role="article"]') ||
(root.contains(element) ? element : null)
);
}
function getCommentRecords() {
const root = getMainContent();
if (!root) {
return [];
}
const candidates = new Set();
root.querySelectorAll("[data-comment-id], [data-comment-pk]").forEach((element) => {
candidates.add(element);
});
root.querySelectorAll('a[href*="/c/"], a[href*="comment_id="]').forEach((link) => {
const comment = getCommentContainer(link, root);
if (comment) {
candidates.add(comment);
}
});
root.querySelectorAll("li time[datetime]").forEach((time) => {
const comment = getCommentContainer(time, root);
if (comment) {
candidates.add(comment);
}
});
const seenItems = new Set();
const seenKeys = new Set();
const records = [];
candidates.forEach((candidate) => {
const item = getCommentContainer(candidate, root);
if (
!item ||
seenItems.has(item) ||
!isCountableElement(item, MIN_COMMENT_WIDTH, MIN_COMMENT_HEIGHT)
) {
return;
}
seenItems.add(item);
const key = getCommentKey(item);
if (!key || seenKeys.has(key)) {
return;
}
seenKeys.add(key);
records.push({ item, key });
});
return sortRecordsInDocumentOrder(records);
}
function getCountableRecords() {
const surface = getSurface();
if (!surface.active) {
return [];
}
return surface.kind === "comments" ? getCommentRecords() : getMediaRecords();
}
function rememberItemOrder(records) {
records.forEach((record, index) => {
if (orderedKeys.includes(record.key)) {
return;
}
const previousKnownKey = findNearbyKnownKey(records, index - 1, -1);
const nextKnownKey = findNearbyKnownKey(records, index + 1, 1);
if (previousKnownKey) {
orderedKeys.splice(orderedKeys.indexOf(previousKnownKey) + 1, 0, record.key);
} else if (nextKnownKey) {
orderedKeys.splice(orderedKeys.indexOf(nextKnownKey), 0, record.key);
} else {
orderedKeys.push(record.key);
}
});
}
function findNearbyKnownKey(records, startIndex, direction) {
for (
let index = startIndex;
index >= 0 && index < records.length;
index += direction
) {
if (orderedKeys.includes(records[index].key)) {
return records[index].key;
}
}
return "";
}
function applyBrake() {
resetAfterNavigation();
const settleTimeLeft = routeSettlesAt - Date.now();
if (settleTimeLeft > 0) {
scheduleApplyBrake(settleTimeLeft);
return;
}
const surface = getSurface();
if (!surface.active) {
unlockBrake();
return;
}
if (surface.kind === "reels") {
applyReelsBrake();
return;
}
const records = getCountableRecords();
rememberItemOrder(records);
if (orderedKeys.length < visibleItemLimit) {
unlockBrake();
return;
}
const boundary = findBoundaryRecord(records);
if (!boundary) {
unlockBrake();
updateBrakeText();
return;
}
lockAtBoundary(boundary, records);
}
function getVisibleRecordScore(record, scroller) {
const rect = record.item.getBoundingClientRect();
const scrollerRect = scroller === window
? { top: 0, bottom: window.innerHeight }
: scroller.getBoundingClientRect();
const visibleTop = Math.max(rect.top, scrollerRect.top);
const visibleBottom = Math.min(rect.bottom, scrollerRect.bottom);
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
const viewportCenter = (scrollerRect.top + scrollerRect.bottom) / 2;
const itemCenter = (rect.top + rect.bottom) / 2;
return visibleHeight * 1000 - Math.abs(viewportCenter - itemCenter);
}
function findBestVisibleRecord(records) {
let bestRecord = null;
let bestScore = -Infinity;
records.forEach((record) => {
const scroller = findScrollContainer(record.item);
const score = getVisibleRecordScore(record, scroller);
if (score > bestScore) {
bestRecord = record;
bestScore = score;
}
});
return bestRecord;
}
function getActiveReelRecord(records) {
const routeMedia = getMediaInfoFromHref(location.href);
const routeRecord = routeMedia
? records.find((record) => record.key === routeMedia.key)
: null;
const visibleRecord = routeRecord || findBestVisibleRecord(records);
if (routeMedia) {
return {
item: visibleRecord ? visibleRecord.item : getMainContent(),
key: routeMedia.key,
};
}
return visibleRecord;
}
function rememberViewedReel(record) {
activeReelKey = record ? record.key : activeReelKey;
if (activeReelKey && !orderedKeys.includes(activeReelKey)) {
orderedKeys.push(activeReelKey);
}
}
function applyReelsBrake() {
const records = getMediaRecords();
const activeRecord = getActiveReelRecord(records);
rememberViewedReel(activeRecord);
if (orderedKeys.length < visibleItemLimit) {
unlockBrake();
return;
}
if (!activeRecord) {
if (isLocked) {
showBrake();
updateBrakeText();
}
return;
}
const activeIndex = orderedKeys.indexOf(activeRecord.key);
const boundaryIndex = visibleItemLimit - 1;
if (isLocked && lockScroller && activeIndex < boundaryIndex) {
showBrake();
updateBrakeText();
clampScrollToBrake(lockScroller);
return;
}
if (activeIndex >= boundaryIndex) {
lockAtCurrentReel(activeRecord);
}
}
function lockAtCurrentReel(record) {
const nextScroller = findScrollContainer(record.item);
const scrollerChanged = lockScroller !== nextScroller;
let nextMaxPosition = getScrollPosition(nextScroller);
if (minPositionForNextLock !== null) {
nextMaxPosition = Math.max(nextMaxPosition, minPositionForNextLock);
minPositionForNextLock = null;
}
setMaxScrollPosition(nextMaxPosition, nextScroller);
isLocked = true;
lockScroller = nextScroller;
if (scrollerChanged) {
lastScrollPosition = getScrollPosition(nextScroller);
}
showBrake();
updateBrakeText();
clampScrollToBrake(nextScroller);
pauseInactiveReelMedia(record.item);
}
function pauseInactiveReelMedia(activeItem) {
const root = getMainContent();
if (!root) {
return;
}
root.querySelectorAll("video").forEach((video) => {
if (video !== activeItem && !activeItem.contains(video)) {
pauseMediaInElement(video);
}
});
}
function findBoundaryRecord(records) {
const anchorKey = orderedKeys[visibleItemLimit - 1];
const anchorRecord = records.find((record) => record.key === anchorKey);
if (anchorRecord) {
return { edge: "bottom", record: anchorRecord };
}
const firstHiddenRecord = records.find(
(record) => orderedKeys.indexOf(record.key) >= visibleItemLimit
);
return firstHiddenRecord ? { edge: "top", record: firstHiddenRecord } : null;
}
function findScrollContainer(element) {
let current = element.parentElement;
while (current && current !== document.body && current !== document.documentElement) {
const style = window.getComputedStyle(current);
const overflowY = style.overflowY || style.overflow;
if (/(auto|scroll|overlay)/.test(overflowY) && current.scrollHeight > current.clientHeight + 1) {
return current;
}
current = current.parentElement;
}
return window;
}
function getScrollPosition(scroller) {
return scroller === window ? window.scrollY : scroller.scrollTop;
}
function getViewportHeight(scroller) {
return scroller === window ? window.innerHeight : scroller.clientHeight;
}
function getBoundaryPosition(boundary, scroller) {
const rect = boundary.record.item.getBoundingClientRect();
const edgePosition = boundary.edge === "top" ? rect.top : rect.bottom;
if (scroller === window) {
return window.scrollY + edgePosition;
}
const scrollerRect = scroller.getBoundingClientRect();
return scroller.scrollTop + edgePosition - scrollerRect.top;
}
function lockAtBoundary(boundary, records) {
const nextScroller = findScrollContainer(boundary.record.item);
const scrollerChanged = lockScroller !== nextScroller;
const boundaryPosition = getBoundaryPosition(boundary, nextScroller);
let nextMaxPosition = Math.max(
0,
boundaryPosition - getViewportHeight(nextScroller) + BRAKE_PANEL_HEIGHT
);
if (minPositionForNextLock !== null) {
nextMaxPosition = Math.max(nextMaxPosition, minPositionForNextLock);
minPositionForNextLock = null;
}
setMaxScrollPosition(nextMaxPosition, nextScroller);
isLocked = true;
lockScroller = nextScroller;
if (scrollerChanged) {
lastScrollPosition = getScrollPosition(nextScroller);
}
showBrake();
updateBrakeText();
clampScrollToBrake(nextScroller);
pauseMediaAfterLimit(records);
}
function setMaxScrollPosition(nextMaxPosition, nextScroller) {
if (
!isLocked ||
maxScrollPosition === null ||
lockScroller !== nextScroller ||
nextMaxPosition < maxScrollPosition ||
Date.now() <= allowMaxIncreaseUntil
) {
maxScrollPosition = nextMaxPosition;
}
}
function unlockBrake() {
isLocked = false;
maxScrollPosition = null;
lockScroller = null;
hideBrake();
}
function ensureBrake() {
const existingBrake = document.getElementById(BRAKE_ID);
if (existingBrake) {
return existingBrake;
}
const brake = document.createElement("section");
brake.id = BRAKE_ID;
brake.hidden = true;
brake.setAttribute("role", "region");
brake.setAttribute("aria-label", "IG Scroll Brake controls");
brake.innerHTML = `
<div class="isb-inner">
<div class="isb-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="5"></rect>
<circle cx="12" cy="12" r="4"></circle>
<circle cx="17.5" cy="6.5" r="1" fill="currentColor" stroke="none"></circle>
</svg>
</div>
<div class="isb-title">You’ve reached your scroll brake</div>
<div class="isb-status" aria-live="polite"></div>
<div class="isb-controls">
<button type="button">Load more</button>
<label>
<span>At a time</span>
<input type="number" min="${MIN_ITEMS_PER_CLICK}" max="${MAX_ITEMS_PER_CLICK}" step="1" inputmode="numeric" aria-label="Items to load at a time">
</label>
</div>
</div>
`;
const input = brake.querySelector("input");
const button = brake.querySelector("button");
input.value = String(itemsPerClick);
input.addEventListener("change", syncItemsPerClickFromInput);
input.addEventListener("blur", syncItemsPerClickFromInput);
input.addEventListener("input", updateBrakeText);
button.addEventListener("click", () => {
resetAfterNavigation();
clearScheduledScan();
syncItemsPerClickFromInput();
minPositionForNextLock = lockScroller ? getScrollPosition(lockScroller) : null;
allowMaxIncreaseUntil = Date.now() + EXPAND_LOCK_GRACE_MS;
visibleItemLimit += itemsPerClick;
unlockBrake();
scheduleApplyBrake(0);
});
document.body.appendChild(brake);
return brake;
}
function showBrake() {
ensureBrake().hidden = false;
}
function hideBrake() {
const brake = document.getElementById(BRAKE_ID);
if (brake) {
brake.hidden = true;
}
}
function syncItemsPerClickFromInput() {
const input = document.querySelector(`#${BRAKE_ID} input`);
if (!input) {
return;
}
itemsPerClick = clampWholeNumber(
input.value,
itemsPerClick,
MIN_ITEMS_PER_CLICK,
MAX_ITEMS_PER_CLICK
);
input.value = String(itemsPerClick);
saveItemsPerClick();
updateBrakeText();
}
function updateBrakeText() {
const brake = ensureBrake();
const input = brake.querySelector("input");
const button = brake.querySelector("button");
const status = brake.querySelector(".isb-status");
const surface = getSurface();
const typedCount = input ? input.value : itemsPerClick;
const previewCount = clampWholeNumber(
typedCount,
itemsPerClick,
MIN_ITEMS_PER_CLICK,
MAX_ITEMS_PER_CLICK
);
const shownCount = Math.min(visibleItemLimit, orderedKeys.length);
const previewLabel = previewCount === 1 ? surface.singular : surface.plural;
const shownLabel = shownCount === 1 ? surface.singular : surface.plural;
const buttonText = `Load ${previewCount} more ${previewLabel}`;
const statusText = `Showing ${shownCount} ${shownLabel} · scrolling paused`;
if (button && button.textContent !== buttonText) {
button.textContent = buttonText;
}
if (input && document.activeElement !== input) {
input.value = String(itemsPerClick);
}
if (status && status.textContent !== statusText) {
status.textContent = statusText;
}
}
function pauseMediaAfterLimit(records) {
records.forEach((record) => {
const keyIndex = orderedKeys.indexOf(record.key);
if (keyIndex >= visibleItemLimit) {
pauseMediaInElement(record.item);
}
});
}
function pauseMediaInElement(element) {
const mediaElements = element.matches("audio, video")
? [element]
: Array.from(element.querySelectorAll("audio, video"));
mediaElements.forEach((media) => {
try {
media.pause();
} catch {
// Some browser media shims throw when pause is unavailable.
}
media.autoplay = false;
media.removeAttribute("autoplay");
});
}
function scheduleApplyBrake(delay) {
const routeChanged = resetAfterNavigation();
if (scanTimer !== null) {
return;
}
const wait = routeChanged
? ROUTE_SETTLE_DELAY_MS
: clampWholeNumber(delay, SCAN_DELAY_MS, 0, ROUTE_SETTLE_DELAY_MS);
scanTimer = window.setTimeout(() => {
scanTimer = null;
applyBrake();
}, wait);
}
function clearScheduledScan() {
if (scanTimer === null) {
return;
}
window.clearTimeout(scanTimer);
scanTimer = null;
}
function resetAfterNavigation() {
const nextRouteKey = getRouteKey();
if (nextRouteKey === currentRouteKey) {
return false;
}
saveCurrentRouteState();
currentRouteKey = nextRouteKey;
if (!restoreRouteState(nextRouteKey)) {
resetCurrentProgress();
}
return true;
}
function saveCurrentRouteState() {
routeStates.delete(currentRouteKey);
routeStates.set(currentRouteKey, {
activeReelKey,
visibleItemLimit,
orderedKeys: orderedKeys.slice(),
});
while (routeStates.size > MAX_SAVED_ROUTE_STATES) {
const oldestKey = routeStates.keys().next().value;
routeStates.delete(oldestKey);
}
}
function restoreRouteState(routeKey) {
const state = routeStates.get(routeKey);
if (!state) {
return false;
}
visibleItemLimit = clampWholeNumber(
state.visibleItemLimit,
getInitialVisibleItemLimit(),
1,
Number.MAX_SAFE_INTEGER
);
activeReelKey = state.activeReelKey || "";
orderedKeys.length = 0;
state.orderedKeys.forEach((key) => {
if (key && !orderedKeys.includes(key)) {
orderedKeys.push(key);
}
});
routeSettlesAt = Date.now() + ROUTE_SETTLE_DELAY_MS;
minPositionForNextLock = null;
allowMaxIncreaseUntil = 0;
clearScheduledScan();
unlockBrake();
return true;
}
function resetCurrentProgress() {
routeStates.delete(currentRouteKey);
visibleItemLimit = getInitialVisibleItemLimit();
activeReelKey = "";
orderedKeys.length = 0;
routeSettlesAt = Date.now() + ROUTE_SETTLE_DELAY_MS;
minPositionForNextLock = null;
allowMaxIncreaseUntil = 0;
clearScheduledScan();
unlockBrake();
}
function setScrollPosition(scroller, position) {
if (scroller === window) {
window.scrollTo({
top: position,
left: window.scrollX,
behavior: "auto",
});
return;
}
if (typeof scroller.scrollTo === "function") {
scroller.scrollTo({ top: position, left: scroller.scrollLeft, behavior: "auto" });
} else {
scroller.scrollTop = position;
}
}
function clampScrollToBrake(eventOrScroller) {
const eventTarget = eventOrScroller && eventOrScroller.target;
const scroller = eventOrScroller instanceof Element || eventOrScroller === window
? eventOrScroller
: lockScroller || window;
if (eventTarget && lockScroller && lockScroller !== window && eventTarget !== lockScroller) {
return;
}
const currentPosition = getScrollPosition(scroller);
if (resetAfterNavigation()) {
lastScrollPosition = currentPosition;
scheduleApplyBrake(ROUTE_SETTLE_DELAY_MS);
return;
}
if (getSurface().kind === "reels") {
scheduleApplyBrake(0);
}
if (shouldResetAfterTopJump(currentPosition)) {
resetCurrentProgress();
lastScrollPosition = currentPosition;
scheduleApplyBrake(ROUTE_SETTLE_DELAY_MS);
return;
}
if (
!isLocked ||
maxScrollPosition === null ||
scroller !== lockScroller ||
currentPosition <= maxScrollPosition
) {
lastScrollPosition = currentPosition;
return;
}
setScrollPosition(scroller, maxScrollPosition);
lastScrollPosition = maxScrollPosition;
}
function shouldResetAfterTopJump(currentPosition) {
return (
isLocked &&
maxScrollPosition !== null &&
currentPosition <= TOP_RESET_SCROLL_Y &&
lastScrollPosition - currentPosition >= TOP_RESET_MIN_DISTANCE
);
}
function isEditableTarget(target) {
if (!(target instanceof Element)) {
return false;
}
return Boolean(target.closest('input, textarea, select, [contenteditable="true"]'));
}
function isActivationKeyTarget(event) {
if (event.type !== "keydown" || event.key !== " " || !(event.target instanceof Element)) {
return false;
}
return Boolean(event.target.closest('button, summary, [role="button"], [role="menuitem"], [role="option"]'));
}
function getScrollDeltaY(event) {
if (event.type === "wheel") {
if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
return event.deltaY * 16;
}
if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
return event.deltaY * getViewportHeight(lockScroller || window);
}
return event.deltaY;
}
if (event.type === "touchmove") {
const touch = event.touches[0];
if (!touch || lastTouchY === null) {
return 0;
}
return lastTouchY - touch.clientY;
}
if (event.type !== "keydown") {
return 0;
}
const viewportHeight = getViewportHeight(lockScroller || window);
const keyDeltas = {
ArrowDown: 40,
PageDown: viewportHeight * 0.85,
End: Number.MAX_SAFE_INTEGER,
" ": viewportHeight * 0.85,
};
return keyDeltas[event.key] || 0;
}
function findNestedScrollableTarget(target, deltaY) {
if (!(target instanceof Element)) {
return null;
}
let current = target;
while (current && current !== document.body && current !== document.documentElement) {
if (current === lockScroller) {
return null;
}
const style = window.getComputedStyle(current);
const overflowY = style.overflowY || style.overflow;
if (/(auto|scroll|overlay)/.test(overflowY) && current.scrollHeight > current.clientHeight + 1) {
const canScrollDown = current.scrollTop + current.clientHeight < current.scrollHeight - 1;
const canScrollUp = current.scrollTop > 0;
if ((deltaY > 0 && canScrollDown) || (deltaY < 0 && canScrollUp)) {
return current;
}
}
current = current.parentElement;
}
return null;
}
function eventCanAffectLockScroller(event, deltaY) {
if (!lockScroller) {
return false;
}
if (lockScroller !== window) {
return event.target instanceof Node && lockScroller.contains(event.target);
}
return !findNestedScrollableTarget(event.target, deltaY);
}
function preventScrollPastBrake(event) {
if (resetAfterNavigation()) {
scheduleApplyBrake(ROUTE_SETTLE_DELAY_MS);
return;
}
if (
!isLocked ||
maxScrollPosition === null ||
isEditableTarget(event.target) ||
isActivationKeyTarget(event) ||
isBeforeActiveReelBoundary()
) {
return;
}
const deltaY = getScrollDeltaY(event);
if (
deltaY <= 0 ||
!eventCanAffectLockScroller(event, deltaY) ||
getScrollPosition(lockScroller) + deltaY <= maxScrollPosition
) {
return;
}
event.preventDefault();
if (getScrollPosition(lockScroller) < maxScrollPosition) {
setScrollPosition(lockScroller, maxScrollPosition);
}
}
function rememberTouchPosition(event) {
const touch = event.touches[0];
lastTouchY = touch ? touch.clientY : null;
}
function forgetTouchPosition() {
lastTouchY = null;
}
function isAtActiveReelBoundary() {
const activeIndex = orderedKeys.indexOf(activeReelKey);
return getSurface().kind === "reels" && activeIndex >= visibleItemLimit - 1;
}
function isBeforeActiveReelBoundary() {
const activeIndex = orderedKeys.indexOf(activeReelKey);
return getSurface().kind === "reels" && activeIndex >= 0 && activeIndex < visibleItemLimit - 1;
}
function preventReelAdvanceClick(event) {
if (!isLocked || !isAtActiveReelBoundary() || !(event.target instanceof Element)) {
return;
}
const control = event.target.closest('button, [role="button"]');
if (!control || control.closest(`#${BRAKE_ID}`)) {
return;
}
const labelledChild = control.querySelector("[aria-label]");
const label = normalizeText(
`${control.getAttribute("aria-label") || ""} ${control.getAttribute("title") || ""} ${labelledChild ? labelledChild.getAttribute("aria-label") : ""} ${control.textContent || ""}`
);
if (!/\b(next(?: reel)?|down)\b/i.test(label)) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
}
function shouldResetProgressForClick(target) {
if (!(target instanceof Element) || target.closest(`#${BRAKE_ID}`)) {
return false;
}
const control = target.closest('a[href], button, [role="button"]');
if (!control) {
return false;
}
if (control.matches("a[href]")) {
const url = parseUrl(control.getAttribute("href"));
if (url && getRouteKeyForUrl(url) === currentRouteKey) {
return true;
}
}
const label = normalizeText(
`${control.getAttribute("aria-label") || ""} ${control.textContent || ""}`
);
return /\b(new posts?|refresh|scroll to top|back to top)\b/i.test(label);
}
function getRouteKeyForUrl(url) {
const pathname = url.pathname === "/" ? "/" : url.pathname.replace(/\/$/, "");
if (/^\/reels?(?:\/|$)/i.test(pathname)) {
return "/reels/:continuous";
}
if (pathname === "/") {
const variant = url.searchParams.get("variant");
return variant ? `/?variant=${encodeURIComponent(variant)}` : "/";
}
return pathname;
}
function handleProgressResetClick(event) {
if (!shouldResetProgressForClick(event.target)) {
return;
}
resetCurrentProgress();
scheduleApplyBrake(ROUTE_SETTLE_DELAY_MS);
}
function handleTopResetShortcut(event) {
if (isEditableTarget(event.target)) {
return;
}
if (event.key !== "Home" && !((event.metaKey || event.ctrlKey) && event.key === "ArrowUp")) {
return;
}
resetCurrentProgress();
scheduleApplyBrake(ROUTE_SETTLE_DELAY_MS);
}
function installNavigationHooks() {
const notifyNavigation = () => {
const hrefChanged = location.href !== lastObservedHref;
lastObservedHref = location.href;
if (resetAfterNavigation()) {
scheduleApplyBrake(ROUTE_SETTLE_DELAY_MS);
} else if (hrefChanged && isContinuousReelsRoute()) {
scheduleApplyBrake(0);
}
};
["pushState", "replaceState"].forEach((methodName) => {
const originalMethod = history[methodName];
if (typeof originalMethod !== "function" || originalMethod.__igScrollBrakeWrapped) {
return;
}
const wrappedMethod = function () {
const result = originalMethod.apply(this, arguments);
window.setTimeout(notifyNavigation, 0);
return result;
};
Object.defineProperty(wrappedMethod, "__igScrollBrakeWrapped", { value: true });
history[methodName] = wrappedMethod;
});
window.addEventListener("popstate", notifyNavigation);
window.setInterval(notifyNavigation, 200);
}
function mutationsNeedScan(mutations) {
return mutations.some((mutation) => {
if (mutation.target instanceof Element && mutation.target.closest(`#${BRAKE_ID}`)) {
return false;
}
const changedNodes = [...mutation.addedNodes, ...mutation.removedNodes];
return !changedNodes.length || changedNodes.some((node) => {
if (node instanceof Element && node.id === BRAKE_ID) {
return !node.isConnected;
}
return !(node instanceof Element) || !node.closest(`#${BRAKE_ID}`);
});
});
}
function boot() {
addStyles();
ensureBrake();
installNavigationHooks();
applyBrake();
const observer = new MutationObserver((mutations) => {
if (mutationsNeedScan(mutations)) {
scheduleApplyBrake();
}
});
observer.observe(document.body, {
attributes: true,
attributeFilter: ["aria-hidden", "data-comment-id", "data-media-id", "hidden", "href", "poster", "src"],
childList: true,
subtree: true,
});
window.addEventListener("scroll", clampScrollToBrake, { passive: true });
document.addEventListener("scroll", clampScrollToBrake, { capture: true, passive: true });
window.addEventListener("click", preventReelAdvanceClick, { capture: true, passive: false });
window.addEventListener("click", handleProgressResetClick, { capture: true, passive: true });
window.addEventListener("wheel", preventScrollPastBrake, { capture: true, passive: false });
window.addEventListener("keydown", handleTopResetShortcut, { capture: true });
window.addEventListener("keydown", preventScrollPastBrake, { capture: true });
window.addEventListener("touchstart", rememberTouchPosition, { capture: true, passive: true });
window.addEventListener("touchmove", preventScrollPastBrake, { capture: true, passive: false });
window.addEventListener("touchmove", rememberTouchPosition, { capture: true, passive: true });
window.addEventListener("touchend", forgetTouchPosition, { capture: true, passive: true });
window.addEventListener("touchcancel", forgetTouchPosition, { capture: true, passive: true });
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", boot, { once: true });
} else {
boot();
}
})();