├── 12ft.io.js ├── kill-gifs.js ├── grayscale.js ├── paywall-sweeper.js ├── subpxl.js ├── force-system-font-stack.js ├── limit-image-width-in-gitlab-mrs.js ├── addCustomCSS.js ├── removeCustomCSS.js ├── show-linked-media-in-page.js ├── a-la-instapaper.js ├── aa-non-subpixel.js ├── README.md ├── mark-visited-links.js ├── edit-webpage.js ├── LICENSE ├── page-or-selection-to-markdown.js ├── funky-focus.js ├── pip.js ├── open-image-links-in-new-page.js ├── open-image-in-new-page.js ├── enable-text-selection.js ├── font-stack-guess.js └── text-selection-demo.html /12ft.io.js: -------------------------------------------------------------------------------- 1 | const loc = document.location; 2 | if (typeof loc == 'undefined') { 3 | alert ('It doesn\'t look like you are currently viewing a page. (Location undefined)'); 4 | } 5 | else document.location = `https://12ft.io/${loc}`; 6 | -------------------------------------------------------------------------------- /kill-gifs.js: -------------------------------------------------------------------------------- 1 | var imgs = Array.prototype.slice.call(document.getElementsByTagName('img')); 2 | imgs.forEach(function (img) { 3 | if (img.src.substr(-4).toUpperCase() === '.GIF') { 4 | img.src = '//i.imgur.com/vMgrL.png'; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /grayscale.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var s=(document.getElementsByTagName('head')[0]||document.body).appendChild(document.createElement('style')); 3 | var t=document.createTextNode('body { filter: grayscale(100%) !important; -webkit-filter: grayscale(100%) !important;}'); 4 | s.appendChild(t); 5 | } 6 | )(); 7 | -------------------------------------------------------------------------------- /paywall-sweeper.js: -------------------------------------------------------------------------------- 1 | // For when certain sites show you an onerous paywall. 2 | // Compile with jQuery option on at https://alanhogan.github.io/bookmarkleter/ 3 | // Version 0.2 4 | 5 | // Rhymes with Doomblerg 6 | $('#graphics-paywall-overlay, .leaderboard-container, #banner-container').remove(); 7 | $('body, html').removeAttr('data-paywall-overlay-status'); 8 | -------------------------------------------------------------------------------- /subpxl.js: -------------------------------------------------------------------------------- 1 | void(d=document); 2 | void(cs='//alanhogan.com/files/perm/subpixel.css'); 3 | void(d.g=d.getElementsByTagName); 4 | void(l='link'); 5 | void(H=d.g('head').item(0)); 6 | void(c=(d.all)?c=d.all.tags(l):d.g(l)); 7 | with(c){ 8 | for(i=0;i' 10 | ); 11 | -------------------------------------------------------------------------------- /addCustomCSS.js: -------------------------------------------------------------------------------- 1 | /* This should probably have ES6 to ES5 transpilation on when compiling to bookmarklet */ 2 | 3 | /* Suggested bookmarklet names: "+CSS" or "Apply Custom Stylesheet" */ 4 | 5 | const version = '1.1'; 6 | 7 | const styles = prompt('Enter CSS here to add it to the current page.'); 8 | 9 | const styleElement = (document.getElementsByTagName('head')[0]||document.body).appendChild(document.createElement('style')); 10 | styleElement.dataset.ahBookmarkletInserted = "1"; 11 | const textNode = document.createTextNode(styles); 12 | styleElement.appendChild(textNode); 13 | -------------------------------------------------------------------------------- /removeCustomCSS.js: -------------------------------------------------------------------------------- 1 | /* This should probably have ES6 to ES5 transpilation on when compiling to bookmarklet */ 2 | 3 | /* Suggested bookmarklet names: "–CSS" or "Clear Custom Stylesheets" */ 4 | 5 | const version = '1.0'; 6 | 7 | 8 | const customStylesheets = document.querySelectorAll('style[data-ah-bookmarklet-inserted]'); 9 | if (customStylesheets.length < 1) { 10 | alert("No custom stylesheets inserted with the corresponding bookmarklet can been found. https://alanhogan.com/bookmarklets#add_css"); 11 | } 12 | customStylesheets.forEach((stylesheet) => { 13 | stylesheet.parentElement.removeChild(stylesheet); 14 | }); 15 | -------------------------------------------------------------------------------- /show-linked-media-in-page.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const linkedImages = document.querySelectorAll('a[href$=".gif" i], a[href$=".jpeg" i], a[href$=".jpg" i], a[href$=".png" i], a[href$=".apng" i], a[href$=".webp" i], a[href$=".avif" i], a[href$=".svg" i]'); 3 | linkedImages.forEach(function(a) { 4 | a.insertAdjacentHTML('afterend', '
') 5 | }); 6 | const linkedVideos = document.querySelectorAll('a[href$=".mov" i], a[href$=".mp4" i], a[href$=".avi" i], a[href$=".webm" i], a[href$=".wmv" i], a[href$=".flv" i] a[href$=".mkv" i]'); 7 | linkedVideos.forEach(function(a) { 8 | a.insertAdjacentHTML('afterend', '
') 9 | }); 10 | })(); 11 | -------------------------------------------------------------------------------- /a-la-instapaper.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var i, x; 3 | for (i = 0; x = document.styleSheets[i]; ++i) x.disabled = true; 4 | var newSS, styles = 'body { max-width: 25em !important; overflow: wrap; margin-left: auto; margin-right: auto; line-height: 1.4em; } * { background: white ! important; color: black !important; font-face: Georgia !important; } :link, :link * { color: #0011aa !important } :visited, :visited * { color: #440590 !important; } pre, code, key, .code { font-family: monospace !important;} '; 5 | if (document.createStyleSheet) { 6 | document.createStyleSheet("javascript:'" + styles + "'"); 7 | } else { 8 | newSS = document.createElement('link'); 9 | newSS.rel = 'stylesheet'; 10 | newSS.href = 'data:text/css,' + escape(styles); 11 | document.getElementsByTagName("head")[0].appendChild(newSS); 12 | } 13 | })(); 14 | -------------------------------------------------------------------------------- /aa-non-subpixel.js: -------------------------------------------------------------------------------- 1 | var antiAliasMode = true; // set false to compile the subpixel mode. 2 | var version = '2.0'; 3 | var name = 'com.alanhogan.bookmarklets.' + (antiAliasMode ? 'aa' : 'subpixel') + '.' + version; 4 | 5 | var extant = document.querySelector('style[data-via-bookmarklet="'+name+'"]'); 6 | 7 | if (extant !== null) { 8 | console.log(`Bookmarklet (${name}): Already ran on page`); 9 | } 10 | 11 | var css = ` 12 | * { 13 | -webkit-font-smoothing: ${(antiAliasMode ? 'antialiased' : 'auto')} !important; 14 | -moz-osx-font-smoothing: ${(antiAliasMode ? 'grayscale' : 'auto')} !important; 15 | font-smoothing: ${(antiAliasMode ? 'antialiased' : 'auto')} !important; 16 | } 17 | `; 18 | 19 | var styleElem = document.createElement('style'); 20 | styleElem.setAttribute('data-via-bookmarklet', name); 21 | styleElem.innerHTML = css; 22 | 23 | document.head.appendChild(styleElem); 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bookmarklets 2 | 3 | A collection of bookmarklets! 4 | 5 | Bookmarklets (aka favelets) in this repository are stored as nicely formatted JavaScript, easy to edit and understand. They are closely mirrored in more usable form at for easy installation. 6 | 7 | ## Converting to Bookmarklet Form 8 | 9 | I will use an online bookmarklet generator, particularly , which seems the most robust and convenient, to convert these into bookmarklet form. Why? (It’s easy, after all: Just add `javascript:` in front and maybe URL-encode the script, right?) For consistency and reproducibility. (formerly: [1](http://mrcoles.com/bookmarklet/), [2](http://chriszarate.github.io/bookmarkleter/).) 10 | 11 | This repo is here largely so I can have one source of truth: The files in this reposititory. (The alternative is bouncing between bookmarklet and more readable formats, which is less than ideal, and which loses comments.) 12 | -------------------------------------------------------------------------------- /mark-visited-links.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Bookmarkletify at 3 | * https://alanhogan.github.io/bookmarkleter/ 4 | */ 5 | 6 | 7 | var version = '1.2'; 8 | var name = 'com.alanhogan.bookmarklets.mark-visited.' + version; 9 | 10 | var extant = document.querySelector('style[data-via-bookmarklet="'+name+'"]'); 11 | 12 | if (extant !== null) { 13 | console.log(`Bookmarklet (${name}): Already ran on page`); 14 | } 15 | 16 | var css = ` 17 | a:link{ 18 | text-shadow: 0 0.06em 0.3em rgba(100%, 100%, 100%, 1), 19 | -1px 1px 1px rgba(100%, 100%, 100%, 1), 20 | 1px 1px 1px rgba(104%, 100%, 100%, 1), 21 | -1px -1px 1px rgba(100%, 100%, 100%, 1), 22 | 1px -1px 1px rgba(100%, 100%, 100%, 1) !important; 23 | } 24 | a:link, 25 | a:link * { 26 | color: rgb(5%, 27%, 74%) !important; 27 | } 28 | a:visited, 29 | a:visited * { 30 | color: rgb(60%, 2%, 53%) !important; 31 | } 32 | `; 33 | 34 | var styleElem = document.createElement('style'); 35 | styleElem.setAttribute('data-via-bookmarklet', name); 36 | styleElem.innerHTML = css; 37 | 38 | document.head.appendChild(styleElem); 39 | -------------------------------------------------------------------------------- /edit-webpage.js: -------------------------------------------------------------------------------- 1 | const alert = document.createElement('div'); 2 | alert.style = ` 3 | position: fixed; 4 | top: 0; right: 0; 5 | left: 0; bottom: auto; 6 | z-index: 100000000; 7 | background: #adbd; 8 | backdrop-filter: blur(0.2rem) saturate(1.2); 9 | font-size: 4vmin; 10 | color: #115522; 11 | text-align: center; 12 | padding: 0.4em; 13 | transition: opacity 1s ease-in; 14 | `; 15 | 16 | if (document.body.contentEditable !== 'true') 17 | { 18 | document.body.contentEditable = 'true'; 19 | document.designMode='on'; 20 | 21 | alert.innerText = 'This web page should now be editable!' 22 | } else { 23 | document.body.contentEditable = 'false'; 24 | document.designMode='off'; 25 | 26 | alert.style.color = '#621'; 27 | alert.style.backgroundColor = '#eaad'; 28 | alert.innerText = 'Whole-page editing disabled!' ; 29 | } 30 | 31 | 32 | 33 | // '(This is the edit-webpage bookmarklet from at alanhogan.com/bookmarklets)'; 34 | 35 | document.body.appendChild(alert); 36 | 37 | setTimeout(() => { 38 | alert.style.opacity = 0; 39 | }, 40 | 3000); 41 | 42 | setTimeout(() => { 43 | alert.remove(); 44 | }, 45 | 4000); 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alan Hogan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /page-or-selection-to-markdown.js: -------------------------------------------------------------------------------- 1 | // crunch at https://alanhogan.github.io/bookmarkleter/ 2 | 3 | // todo: turn relative URLs into fully qualified ones. 4 | // see https://stackoverflow.com/questions/43261898/in-vanilla-javascript-turn-relative-path-base-url-into-absolute-url 5 | 6 | var html = false, 7 | selection, content, range, div, title; 8 | 9 | try { 10 | if (window.getSelection && (selection = window.getSelection()) && selection.toString().length > 0) { 11 | range = selection.getRangeAt(0); 12 | content = range.cloneContents(); // document fragment 13 | div = document.createElement('div'); 14 | div.appendChild(content.cloneNode(true)); 15 | if (div.innerHTML.length > 0) { 16 | html = div.innerHTML; 17 | } 18 | title = (document.getElementsByTagName('title')[0] || {innerHTML: 'source'}).innerHTML; 19 | } 20 | } catch (ex) {} 21 | 22 | if (html !== false) { 23 | window.location.href = 'http://heckyesmarkdown.com/go/?read=0&showframe=1&html=' + encodeURIComponent("

[" + title.replace(']', '\]') + "](" + document.location.href + ")

\n" + html); 24 | } else { 25 | window.location.href = 'http://heckyesmarkdown.com/go/?read=1&showframe=1&u=' + encodeURIComponent(document.location.href); 26 | } 27 | -------------------------------------------------------------------------------- /funky-focus.js: -------------------------------------------------------------------------------- 1 | /* v1.2 */ 2 | const style = document.createElement('style'); 3 | style.type = 'text/css'; 4 | style.innerText = ` 5 | 6 | @keyframes ffwiggle { 7 | 0% { 8 | transform: translateX(0); 9 | } 10 | 5% { 11 | transform: translateX(-5px); 12 | } 13 | 10% { 14 | transform: translateX(5px); 15 | } 16 | 15% { 17 | transform: translateX(-5px); 18 | } 19 | 20% { 20 | transform: translateX(5px); 21 | } 22 | 25% { 23 | transform: translateX(-5px); 24 | } 25 | 30% { 26 | transform: translateX(5px); 27 | } 28 | 35% { 29 | transform: translateX(0); 30 | } 31 | 100% { 32 | transform: translateX(0); 33 | } 34 | } 35 | 36 | 37 | :focus { 38 | /* FYI: Filters won't work on non-replaced inline boxes e.g. spans, links, etc without a change in the display property */ 39 | filter: hue-rotate(-24deg) brightness(1.1) !important; 40 | outline: #eebb22a0 solid 5px !important; 41 | outline-offset: -2px !important; 42 | text-shadow: 0.04em 0.04em 0.04em #0008, -0.04em -0.04em 0.04em #fff8; 43 | 44 | backdrop-filter: contrast(0.7); 45 | 46 | animation-name: ffwiggle; 47 | animation-duration: 6s; 48 | animation-fill-mode: both; 49 | animation-iteration-count: infinite; 50 | } 51 | `; 52 | document.head.appendChild(style); 53 | -------------------------------------------------------------------------------- /pip.js: -------------------------------------------------------------------------------- 1 | const VERSION = "1.1.1"; 2 | const messageSuffix = "\n\n--pip.js version v"+VERSION+"\nalanhogan.com/bookmarklets"; 3 | 4 | if (!document.pictureInPictureEnabled) { 5 | alert("Your browser doesn’t support picture-in-picture!" + messageSuffix); 6 | return; 7 | } 8 | 9 | const videos = document.getElementsByTagName("video"); 10 | if (videos.length < 1) { 11 | alert("No HTML5 videos were detected on page! Note: The current version of this script does not deal with iFrames" + (document.getElementsByTagName('iframe').length ? " and there is at least one on the page." : ", but there are none detected on this page." ) + messageSuffix); 12 | return; 13 | } 14 | 15 | 16 | let curPipFound = false; 17 | for (let i = 0; i /g, '>').replace(/Images' + 21 | '' + 25 | '' + 26 | '

Images linked to by ' + 27 | hE(location.href) + 28 | ':


' 29 | ); 30 | 31 | var seen = {}, 32 | q, h, i, isVideo; 33 | 34 | for (i = 0; q = document.links[i]; ++i) { 35 | h = q.href; 36 | if (seen[h]) continue; 37 | seen[h] = true; 38 | if (h && isMediaURL(h)) { 39 | isVideo = isVideoURL(h); 40 | z.write('

' + 41 | q.innerHTML + ' (' + hE(h) + ')
' + 42 | (isVideo ? '' : '" />') 46 | ) 47 | } 48 | } 49 | 50 | seen = {}; 51 | 52 | z.write(''); 53 | z.close() 54 | -------------------------------------------------------------------------------- /open-image-in-new-page.js: -------------------------------------------------------------------------------- 1 | // Shrink with https://alanhogan.github.io/bookmarkleter/ 2 | // A minor fork from open-linked-images-in-new-page. 3 | // Finds image tags. Opens a new tab and displays all of them (if the src attribute looks reasonable) 4 | // Not always useful, but sometimes very useful! 5 | 6 | function isMediaURL(u) { 7 | return !!(u.match(/(^data:image\/)|(\.(jpe?g|gif|png|webp|mng)$)/i) || isVideoURL(u)); 8 | } 9 | 10 | function isVideoURL(u) { 11 | return !!u.match(/\.(webm|m4v|flv|mp4|avi|qt)/i); 12 | } 13 | 14 | function hE(s) { 15 | return s.replace(/&/g, '&').replace(/>/g, '>').replace(/Images' + 21 | '' + 25 | '' + 26 | '

Images shown on ' + 27 | hE(location.href) + 28 | ':


' 29 | ); 30 | 31 | var seen = {}, 32 | q, h, i, isVideo; 33 | 34 | for (i = 0; q = document.images[i]; ++i) { 35 | h = q.src; 36 | if (seen[h]) continue; 37 | seen[h] = true; 38 | if (h && isMediaURL(h)) { 39 | isVideo = false; // isVideoURL(h); 40 | z.write('

' + 41 | q.innerHTML + ' (' + hE(h) + ')
' + 42 | (isVideo ? '' : '" />') 46 | ) 47 | } 48 | } 49 | 50 | seen = {}; 51 | 52 | z.write(''); 53 | z.close() 54 | -------------------------------------------------------------------------------- /enable-text-selection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds CSS rules to undo user-select:none. Also cycles through every child of and binds events associated 3 | * with text selection to a benign function. Finally, remove 'disabled' attributes from all text inputs and 4 | * ensures that keydown and keyup events are allowed (to defeat blocks that prevent cut,copy, paste using shortcuts 5 | * There is one concern though: binding to so many events will undoubtedly break functionality on some pages. For 6 | * instance, text inputs very often have custom events bound to keypresses (eg to run a search and show results as 7 | * the user types). As a result, this script is probably not fit to run indiscriminately on all pages; it should 8 | * rather be used only when the user needs to defeat text-selection blocks on specific pages. 9 | * 10 | * Crunch at https://alanhogan.github.io/bookmarkleter/ 11 | */ 12 | 13 | function allowTextSelection() { 14 | window.console && console.log("allowTextSelection"); 15 | 16 | //add styles that enable text selection 17 | var style = document.createElement("style"); 18 | style.type = "text/css"; 19 | style.innerHTML = `*, p, div { 20 | user-select: text !important; 21 | -moz-user-select: text !important; 22 | -webkit-user-select:text !important;} 23 | `; 24 | 25 | document.head.appendChild(style); 26 | 27 | //Put all of children in a collection 28 | //Use getElementsByTagName because it has better compatibility (it's older) than querySelectorAll('*') 29 | var elArray = document.body.getElementsByTagName("*"); 30 | 31 | //allow mouse events typically involved in selection 32 | for (var i = 0; i < elArray.length; i++) { 33 | var el = elArray[i]; 34 | el.onselectstart = el.ondragstart = el.ondrag = el.oncontextmenu = el.onmousedown = el.onmouseup = function() { 35 | return true; 36 | }; 37 | 38 | //special processing for text-style elements 39 | if ( 40 | el instanceof HTMLInputElement && 41 | ["text", "password", "email", "number", "tel", "url"].indexOf( 42 | el.type.toLowerCase() 43 | ) > -1 44 | ) { 45 | //enable text inputs (to defeat an easy way to block selection by setting input's 'disabled' attribute) 46 | el.removeAttribute("disabled"); 47 | 48 | //counteract any listener that would block copy&paste keyboard shortcuts. (I can't figure out yet why 49 | // although this works on the first text input in text-selection-demo.html, it doesn't work on the 2nd 50 | el.onkeydown = el.onkeyup = function() { 51 | return true; 52 | }; 53 | } 54 | } 55 | } 56 | 57 | allowTextSelection(); 58 | -------------------------------------------------------------------------------- /font-stack-guess.js: -------------------------------------------------------------------------------- 1 | // Detector from 2 | // Other code by Alan Hogan 3 | // http://www.apache.org/licenses/LICENSE-2.0 4 | // Compile this to a bookmarklet at 5 | // (originally this bookmarklet used ) 6 | // Some code has been previously converted by the Babel REPL to survive 7 | // running in IE11. Avoid ES6 or transpile before committing updates. 8 | 9 | var versionStr = '2.1'; 10 | 11 | var Detector = function Detector() { 12 | var baseFonts = ['monospace', 'sans-serif', 'serif']; 13 | 14 | var testString = "mmmmmmmmmmlli"; 15 | 16 | var testSize = '72px'; 17 | 18 | var h = document.getElementsByTagName("body")[0]; 19 | 20 | var s = document.createElement("span"); 21 | s.style.fontSize = testSize; 22 | s.innerHTML = testString; 23 | var defaultWidth = {}; 24 | var defaultHeight = {}; 25 | for (var index in baseFonts) { 26 | s.style.fontFamily = baseFonts[index]; 27 | h.appendChild(s); 28 | defaultWidth[baseFonts[index]] = s.offsetWidth; 29 | defaultHeight[baseFonts[index]] = s.offsetHeight; 30 | h.removeChild(s); 31 | } 32 | 33 | function quotedFontIfNecessary(fontName) { 34 | if(/\s/.test(fontName)) { return '\'' + fontName + '\''; } 35 | else return fontName; 36 | } 37 | 38 | function detect(font) { 39 | var detected = false; 40 | for (var index in baseFonts) { 41 | s.style.fontFamily = quotedFontIfNecessary(font) + ', ' + baseFonts[index]; 42 | h.appendChild(s); 43 | var matched = s.offsetWidth != defaultWidth[baseFonts[index]] || s.offsetHeight != defaultHeight[baseFonts[index]]; 44 | h.removeChild(s); 45 | detected = detected || matched; 46 | } 47 | return detected; 48 | } 49 | 50 | this.detect = detect; 51 | }; 52 | 53 | var d = new Detector(); 54 | 55 | function guessUsedFont(fullReport) { 56 | var body = document.getElementsByTagName('body')[0]; 57 | var stack = body ? window.getComputedStyle(body).fontFamily : ''; 58 | var selectionExamined = false; 59 | 60 | try { 61 | var selection = window.getSelection(); 62 | if (selection.toString().length > 0) { 63 | var selectedEl = selection.anchorNode.parentElement; 64 | stack = window.getComputedStyle(selectedEl).fontFamily; 65 | selectionExamined = !! stack; 66 | } 67 | } catch (err) { } 68 | 69 | 70 | var fonts = stack.split(/\s*,\s*/).map(function (font) { 71 | return font.replace(/^['"]\s*(.+\S)\s*['"]$/, '$1'); 72 | }); 73 | 74 | var probableFont = false; 75 | var availableFonts = [], unavailableFonts = []; 76 | fonts.forEach(function (font) { 77 | if (d.detect(font)) { 78 | probableFont = probableFont || font; 79 | availableFonts.push(font); 80 | } else { 81 | unavailableFonts.push(font); 82 | } 83 | }); 84 | 85 | if (probableFont) { 86 | var reportStr = (selectionExamined ? 'The selected text' : 'The BODY element') + " is probably using the font family or keyword: \n" + probableFont; 87 | if (fullReport) { 88 | reportStr = reportStr + "\n\n" + "All matching font names or keywords in the stack: \n" + availableFonts.join("\n") + "\n\n" + "Unavailable fonts: \n" + (unavailableFonts.length > 0 ? unavailableFonts.join("\n") : '(None)') + "\n\n" + "Font stack as reported by getComputedStyle: \n" + stack; 89 | reportStr = reportStr + "\n\nOnly Latin characters are tested, so detection will fail for Asian fonts, emojis, and system fonts. "; 90 | } else { 91 | reportStr = reportStr + "\n\n" 92 | } 93 | 94 | reportStr = reportStr + "More info and potentially a newer version of this bookmarklet are available at " 95 | + "https://alanhogan.com/bookmarklets" 96 | + (fullReport ? '#font-stack-full' : '#font-stack-guess'); 97 | 98 | reportStr = reportStr + "\n\This is version " + versionStr; 99 | 100 | alert(reportStr); 101 | } else { 102 | alert('Did not detect any fonts'); 103 | } 104 | } 105 | 106 | function fullFontStackReport() { 107 | guessUsedFont(true); 108 | } 109 | 110 | // Uncomment ONE of these to generate the simple vs full version of the bookmarklet. 111 | // guessUsedFont(); 112 | fullFontStackReport(); 113 | -------------------------------------------------------------------------------- /text-selection-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Prevent text selection with CSS/JS 9 | 22 | 23 | 24 |

Demo measures to prevent text selections

25 |
26 |

Using CSS

27 |
28 | Text selection in this section has been disabled using CSS only. We simply set the 29 | user-select property and as well as its prefixed alternatives to 'none'. This 30 | method can be effective because it requires no JavaScript; Because CSS style sheets 31 | are typically loaded in the <HEAD> of the document, they take effect immediately. 32 |

33 | 35 |
36 | 37 |

Using JS events bound by html attributes

38 |
41 | 42 | Text selection in this section has been disabled by setting the HTML attributes 43 | that bind to event handlers. The attributes 'onmousedown', 'onmouseup', 'oncontextmenu', 44 | and 'onselect' were all set to a JavaScript function that blocks typical browser behavior 45 | by calling event.preventDefault(). To the textbox below we also added an onkeydown handler 46 | that prevents the typical copy, cut and paste shortcuts. 47 |

48 | 54 |
55 | 56 |

Using purely Javascript

57 |
58 | Text selection in this section has been disabled by setting 'onmousedown', 'onmouseup'... properties 59 | to the same function as in the previous section. The only difference is that in the HTML attributes 60 | that bind to event handlers were set not in HTML as was done above, but in JavaScript. Selection 61 | within the textbox was disabled in JavaScript as well (not in HTML). In addition, the 'disabled' 62 | attribute was set. This is one of the easiest and most effective ways to prevent 63 | selection because the browser blocks all interactions with the disabled textbox. 64 |

65 | 67 |
68 | 69 | 115 | 116 | --------------------------------------------------------------------------------