Raw Source
Mottie / GitHub Reveal Header

// ==UserScript== // @name GitHub Reveal Header // @version 0.1.4 // @description A userscript that reveals the header when hovering near the top of the screen // @license MIT // @author Rob Garrison // @namespace https://github.com/Mottie // @match https://github.com/* // @match https://gist.github.com/* // @run-at document-idle // @grant GM_addStyle // @icon https://github.githubassets.com/pinned-octocat.svg // @updateURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-reveal-header.user.js // @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-reveal-header.user.js // @supportURL https://github.com/Mottie/GitHub-userscripts/issues // ==/UserScript== (() => {	"use strict";	const topZone = 75, // px from the top of the viewport (add	transitionDelay = 200, // ms before header hides	revealAttr = "data-reveal-header", // attribute added to minimize redraw	// body class names to trigger animation	revealStart = "reveal-header-start",	revealAnimate = "reveal-animated",	// selectors from https://github.com/StylishThemes/GitHub-FixedHeader	headers = [	// .header = logged-in	"body{attr}.logged-in .header",	// .site-header = not-logged-in (removed 8/2017)	"{attr} .site-header",	// .Header = logged-in or not-logged-in (added 8/2017)	"{attr} .Header",	// .Header removed, use .js-header-wrapper with header/.Header-old (3/2019)	"{attr} .js-header-wrapper header",	// #com #header = help.github.com	"{attr} #com #header"	// extra	],	$body = $("body");	let timer, timer2;	GM_addStyle(`	${headers.join(",").replace(/\{attr}/g, "")} {	width: 100%;	z-index: 1000;	}	${headers.join(",").replace(/\{attr}/g, `.${revealAnimate}`)} {	-webkit-transition: transform ease-in ${transitionDelay}ms,	marginTop ease-in ${transitionDelay}ms;	transition: transform ease-in ${transitionDelay}ms,	marginTop ease-in ${transitionDelay}ms;	}	${headers.join(",").replace(/\{attr}/g, `.${revealStart}`)} {	position: fixed;	transform: translate3d(0, -100%, 0);	}	${headers.join(",").replace(/\{attr}/g, `[${revealAttr}]`)} {	position: fixed;	transform: translate3d(0, 0%, 0);	}	body.${revealAnimate} {	-webkit-transition: marginTop ease-in ${transitionDelay}ms;	transition: marginTop ease-in ${transitionDelay}ms;	}	`);	function getHeader() {	return $(`${headers.join(",").replace(/\{attr}/g, "")}`);	}	function getScrollTop() {	// needed for Chrome/Firefox	return window.pageYOffset ||	document.documentElement.scrollTop ||	document.body.scrollTop || 0;	}	function onTransitionEnd(el, callback) {	const listener = () => {	callback();	// remove listener after event fired	el.removeEventListener("transitionend", listener);	el.removeEventListener("webkitTransitionEnd", listener);	};	el.addEventListener("transitionend", listener);	el.addEventListener("webkitTransitionEnd", listener);	}	// A margin top is needed to prevent	function clearMarginTop() {	const $header = getHeader();	$body.style.marginTop = "";	if ($header) {	$header.style.marginTop = "";	}	}	function slideDown(event) {	if (event.clientY < topZone) {	const $header = getHeader();	if ($header) {	onTransitionEnd($header, () => {	$body.classList.remove(revealStart);	});	// add 1px for the border	const headerHeight = ($header.clientHeight + 1) + "px";	$body.style.marginTop = headerHeight;	$header.style.marginTop = "-" + headerHeight;	}	// move header to start position instantly	$body.classList.remove(revealAnimate);	$body.classList.add(revealStart);	clearTimeout(timer);	timer = setTimeout(() => {	$body.classList.add(revealAnimate);	$body.setAttribute(revealAttr, true);	}, transitionDelay * 0.2);	}	}	function slideUp() {	clearTimeout(timer);	clearTimeout(timer2);	if (getScrollTop() > topZone) {	$body.classList.add(...[revealStart, revealAnimate]);	onTransitionEnd(getHeader(), () => {	$body.classList.remove(...[revealStart, revealAnimate]);	clearMarginTop();	});	} else {	clearMarginTop();	}	$body.removeAttribute(revealAttr);	}	function clearTimer() {	clearTimeout(timer);	}	function mouseLeave(event) {	clearTimeout(timer);	// don't slideUp when "mouseleave" triggers on children in header	if (event.target === getHeader()) {	timer = setTimeout(() => {	slideUp();	}, transitionDelay * 1.2);	}	}	function bindHeader() {	const $header = getHeader();	if ($header) {	$header.removeEventListener("mouseenter", clearTimer);	$header.removeEventListener("mouseleave", mouseLeave);	$header.addEventListener("mouseenter", clearTimer);	$header.addEventListener("mouseleave", mouseLeave);	}	}	function init() {	document.addEventListener("mousemove", event => {	if (	event.clientY < topZone &&	getScrollTop() > topZone &&	!$body.hasAttribute(revealAttr)	) {	clearTimeout(timer);	timer = setTimeout(() => {	slideDown(event);	}, transitionDelay * 0.2);	}	clearTimeout(timer2);	// check location of mouse... if outside of header, slideUp	timer2 = setTimeout(() => {	const el = document.elementFromPoint(event.clientX, event.clientY);	if (	$body.hasAttribute(revealAttr) &&	!closest(`${headers.join(",").replace(/\{attr}/g, "")}`, el)	) {	slideUp();	}	}, 2000);	});	document.addEventListener("mouseleave", () => {	if ($body.hasAttribute(revealAttr)) {	slideUp();	}	});	bindHeader();	}	function $(str, el) {	return (el || document).querySelector(str);	}	function closest(selector, el) {	while (el && el.nodeType === 1) {	if (el.matches(selector)) {	return el;	}	el = el.parentNode;	}	return null;	}	document.addEventListener("ghmo:container", bindHeader);	init(); })();