Focus Reading

By lazyttrick Last update Feb 15, 2009 — Installed 222 times.

There are 1 previous version of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name           Focus Reading
// @namespace       meh	
// @description    Focus page elements, fading the rest. Toggle at Greasemonkey menu; click any element to focus; use arrow keys to navigate; Esc to exit.
// @include        http://*
// ==/UserScript==

/*XRAY version 0.91a

Copyright (c) 2007 Western Civilisation pty. ltd.
http://westciv.com/xray/
We aim to open source XRAY once the initial code is stabilized
please email any suggestions, errors, feedback and so  on to john allsopp
john@westciv.com 

Some portions (c) Apple Computer
Some portions adapted from Quirksmode http://quirksmode.org and
a tutorial aby Patrick Hunlock http://www.hunlock.com/
bookmark loading code adapted from leftlogic http://leftlogic.com/lounge/articles/microformats_bookmarklet/
The XRAYHUD style inspired by the Shiira Project's HMBlkAppKit http://shiira.jp/hmblkappkit/en.html
Concept inspired by Left Logic's Microformats Bookmarklet http://leftlogic.com/lounge/articles/microformats_bookmarklet/
itself from an original concept by Jon Hicks http://www.hicksdesign.co.uk/

XRAY uses jQuery, at present for one small but crucial aspect allowing support for Safari 2, though hopefully we'll take advantage of their fine effects in upcoming releases

*/

/* 
Copyright (C) 2007 Apple Computer, Inc.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

const OPACITY = '.7';
var body = document.body;
var on = false;


GM_registerMenuCommand('Toogle Focus Reading', toggle);

	
function GetNextStructuredSibling( objNode ){
		// Copyright Ben Nadel @ KinkySolutions.com 2006
		// Check for a valid starting node.
		if (objNode){
		
			// Travel down the sibling chain looking for a non-text node.
			for ( 
				objNode = objNode.nextSibling ;
				(
					objNode && 
					objNode.nodeType == 3
				) ;
				objNode = objNode.nextSibling
				){
				// Nothing has to happen here. The FOR loop itself is taking care
				// of the node traversing. We don't have to really worry about any
				// error checking as we can't really make it outside of HTML DOM
				// elements without hitting a structured node, or coming up with NULL.
			}
			
		}
				
		
		// Return the sibling node.
		
		if (!objNode) return (objNode);
		if (objNode.nodeName=="SCRIPT") return;
		if ((objNode.nodeName=='WCIECanvas') || (objNode.nodeName=='WCCanvas')) return;
		
		return( objNode );
}
	
function GetPreviousStructuredSibling( objNode ){

	// Travel up the sibling chain looking for a non-text node.
	for (
		objNode = objNode.previousSibling ;
		(
			objNode &&
			objNode.nodeType == 3
		) ;
		objNode = objNode.previousSibling
		){
	// Nothing has to happen here. The FOR loop itself is taking care
	// of the node traversing. We don't have to really worry about any
	// error checking as we can't really make it outside of HTML DOM
	// elements without hitting a structured node, or coming up with NULL.
	}

	// Return the sibling node.
	return( objNode );

}

function GetFirstStructuredChild( theElement ){
		
		// var nodeChildren=theElement.children;
		var nodeChildren=theElement.childNodes;
		
		if(!nodeChildren) return theElement;
		//if it's the head, return the body
		
		if (theElement.nodeName=='HTML') return document.body;
		
		for (var i = 0; i < nodeChildren.length; i++)
		   {
		   	   var aChild=nodeChildren[i];
			   if (aChild.nodeName!=="#text") {
					break;
		   		}
			}
		
		if(!aChild) return theElement;	
		if (aChild.nodeName=="SCRIPT") return theElement;
		if (aChild.nodeName=="#text") return theElement;
		
		return aChild;
}
	
function scrollToElement(theElement){
// http://radio.javaranch.com/pascarello/2005/01/09/1105293729000.html
	
	whereIs=getElementOffsetLocation(theElement);
	elementTop=whereIs[1];
    elementLeft=whereIs[0];
	elementHeight=theElement.offsetHeight
	
	if (elementTop>getClientHeight()+getScrollXY()[1] || elementTop+elementHeight<getScrollXY()[1])
		window.scrollTo(0,elementTop-(getClientHeight()/2));

}	


function addCSS (){

	var theHead = getTag('head');
	//var theCSS = createElement('link', {type:'text/css', rel:'stylesheet', href:'http://westciv.com/xray/xraycore.css'});
	var theCSS = createElement('style');
	theCSS.innerHTML = 'canvas#WCcanvas {'+
	'     position: absolute;'+
	'     top: 0;'+
	'     left: 0;'+
	'     z-index: 9999;'+
	'	}'+
	'#XRAYHUD {'+
	'     position: fixed;'+
	'     top:100px;'+
	'     left:100px;'+
	'     z-index:9999;'+
	'     border: 1px rgb(0,0,0) solid;'+
	'     padding: 0em 0em 0em;'+
	'     font-family: "Lucida Grande", Helvetica, Arial, sans-serif;'+
	'     background-color: rgb(48,53,60);'+
	'     font-size: 8pt;'+
	'     -moz-box-shadow: 0px 0px 2px #777777;'+
	'     box-shadow: 0px 0px 5px #777777;'+
	'     color: #ffffff;'+
	'     cursor: move;'+
	'     width: 42em;'+
	'     line-height: 0em;'+
	'     border-top-left-radius: 8px;'+
	'     border-top-right-radius: 8px;'+
	'     opacity: 0.9; '+
	'}'+
	
	//debug(theCSS);
	
	//this zeros style that the style seet of the page visited may apply
	
	'#XRAYHUD * {'+
	'     text-align: left;'+
	'     color: white;'+
	'     background-color: transparent;'+
	'     list-style-image: none;'+
	'     font-family: "Lucida Grande", Helvetica, Arial, sans-serif;'+
	'     font-size: 1em;'+
	'     line-height: 1.4 '+
	'}'+
	
	'#HUDHierarchy {'+
	'     border-bottom: 1px #74777d solid;'+
	'     padding-bottom: .5em '+
	'}'+
	
	'#XRAYHUD p {'+
	'     padding: 0 .5em;'+
	'     margin: 0; '+
	'}'+
	
	'#XRAYHUD .XRAYtitlebar {'+
	'     padding: 0;'+
	'     text-shadow: 1px 1px 1px #bfbfbf;'+
	'     text-align: center;'+
	'     line-height: 0em;'+
	'     margin-bottom: 1em;'+
	'     margin-top: 8px}'+
	
	'.XRAYclosebox {'+
    	'margin: -6px 0;'+
     	'padding: 0 0 0 12px;'+
     	'float: right;'+
     	//'height: 14px;'+
     	//'width: 8px;'+
     	//'background-image: url('images/closebox.png');'+
     	//'background-repeat: no-repeat;'+
     	'cursor: pointer;}'+
	
	'#XRAYHUD p+ul {'+
	'     padding-top: .5em; '+
	'}'+
	
	'#XRAYHUD ul {'+
	'     padding: 0 .5em;'+
	'     margin: 0 0 .5em 0;'+
	'     }'+
	
	'#XRAYHUD .group ul li {'+
	'     list-style-type: none;'+
	'     list-style-image: none;'+
	'     background-image: none}'+
	
	'#XRAYHUD>.elementInfo {'+
	'     clear: both;'+
	'     margin: .5em 0;'+
	'     border-bottom: 1px #74777c solid;'+
	'     padding-bottom: .5em;'+
	'     font-size: 1.1em;'+
	'     text-align: left; '+
	'}'+
	
	'#XRAYHUD>.elementInfo p {'+
	'     padding-right: 1em; '+
	'}'+
	
	'#XRAYHUD>.group {'+
	'     float: left;'+
	'     margin-top: .5em;'+
	'     margin-left: auto;'+
	'     margin-right: auto}'+
	
	'#XRAYHUD #XRAYabout {'+
	'     clear: both;'+
	'     margin: 0;'+
	'     padding-right: .5em;'+
	'     border-top: 1px #74777b solid;'+
	'     padding-top: .5em;'+
	'     font-size: .9em;'+
	'     background-color: rgb(41,45,50);'+
	'     }'+

	'#XRAYHUD #XRAYabout p {     text-align: right;'+
	'     padding:6px 0}'+

	'#XRAYHUD a:link, #XRAYHUD a:visited, #XRAYHUD a:hover,#XRAYHUD a:active {'+
	'     color: white;'+
	'     text-decoration: none; '+
	'     cursor: pointer;'+
	'     border: none;'+
	'     background-image: none;'+
	'     }'+

	'#XRAYHUD .XRAYdetailedLink {'+
	//'/*     background-image: url('images/about.png');'+
	//'     background-repeat: no-repeat;'+
	//'     background-position: right;'+
	'     padding-right: 6px;'+
	'}'+

	//do the labels
	
	'#XRAYWidthLabel, #XRAYHeightLabel, #XRAYTopLeftLabel {'+
	'     position: absolute;'+
	'     z-index: 1002;'+
	'     background-color: #e16820;'+
	'     border: 2px #ffffff solid;'+
	'     color: #ffffff;'+
	'     font-size: 9pt;'+
	'     padding: .2em;'+
	'     -webkit-box-shadow: 1px 1px 3px #5f5f5f;'+
	'     -moz-box-shadow: 1px 1px 3px #5f5f5f;'+
	'     box-shadow: 1px 1px 3px #5f5f5f;'+
	'     visibility: hidden;'+
	'     font-family: "Lucida Grande", Helvetica, Arial, sans-serif;'+
	'     -webkit-border-radius: 5px;'+
	'     border-radius: 5px; '+
	'}';
	//*/
	
	var theCSS = theHead[0].appendChild(theCSS);
	
}


function windowResized(){
	//called by the resize event on the document
	hideCanvas();
}


function keyPressed(e){
	//called by the onkeypress event on the document

	var keythatwaspressed;
		
	if (window.event) keythatwaspressed = window.event.keyCode;
	else if (e) keythatwaspressed = e.which;
	

			
	switch (keythatwaspressed){
		
		case 27: //esc
		
			toggle();
			
		break;
	
		case 38: //up
				
			if (currentPatient.parentNode){
				while (currentPatient.parentNode.nodeName == '#text'){
					currentPatient = currentPatient.parentNode;
				}

				
				if (!(currentPatient.nodeName=='HTML')){
					xRayElement(currentPatient.parentNode);
				}
				return false;	
			}
		
		break;
	
		case 37: //left
		
		if (GetPreviousStructuredSibling(currentPatient)) xRayElement(GetPreviousStructuredSibling(currentPatient));

		return false;
		
		break;
	
		case 39: //right		
		if (GetNextStructuredSibling(currentPatient)) xRayElement(GetNextStructuredSibling(currentPatient));
		
		return false;
		
		break;
	
		case 40: //down
		
		if (GetFirstStructuredChild(currentPatient)) xRayElement(GetFirstStructuredChild(currentPatient));
		
		return false;
		
		break;
	
	
		default : return true;
	}	
	
}



function getElementOffsetLocation(obj) {
	//adapted from an example at quirksmode.org - a must read resource for javascript, DOM and all web development
	try{
		var curleft = curtop = 0;
		if (obj.offsetParent) {
			curleft = obj.offsetLeft;
			curtop = obj.offsetTop;
			while (obj = obj.offsetParent) {
				curleft += obj.offsetLeft;
				curtop += obj.offsetTop;
			}
		}
	}catch(e){
		//debug("getElementOffsetLocation > exception > "+obj+" > "+e);
	}	
	return [curleft,curtop];
}


function insertCanvas () {
	// inserts a canvas element to do the drawing
	var theCanvas = createElement('CANVAS', {id:'WCcanvas', width:getDocumentWidth(), height:getDocumentHeight()});
	body.appendChild(theCanvas);
	theCanvas.addEventListener('mousedown',hideCanvas,false);
}




function deleteNodeByID(nodeId) {
	//delete the node with the given id, if it exists
	
	try {
		var theNode = getId(nodeId);
		if (theNode) {
			theNode.parentNode.removeChild(theNode);
		}
	}
	
	catch (err) {
		
	}
	
	
}


function hideCanvas() {
	
	var canvas = getId("WCcanvas");
	canvas.style.visibility='hidden';	

}


function showCanvas() {
	var canvas = getId("WCcanvas");
 	//canvas.style.display='block';
	canvas.style.visibility='visible';

}


function getClientSize() {
	//http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
	var myWidth = 0, myHeight = 0;
	  if( typeof( window.innerWidth ) == 'number' ) {
	    //Non-IE
	    myWidth = window.innerWidth;
	    myHeight = window.innerHeight;
	  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
	    //IE 6+ in 'standards compliant mode'
	    myWidth = document.documentElement.clientWidth;
	    myHeight = document.documentElement.clientHeight;
	  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
	    //IE 4 compatible
	    myWidth = document.body.clientWidth;
	    myHeight = document.body.clientHeight;
	  }
	return [myHeight, myWidth];	
	}
	

function getClientHeight(){
	//return the height of the display area

	theSize=getClientSize();
	theHeight=theSize[0];

	return theHeight
}

function getClientWidth(){
	//return the height of the display area
	
	theSize=getClientSize();
	theWidth=theSize[1];
    
	return theWidth
}

function getDocumentWidth() {
	if (document.body.scrollWidth)
		return document.body.scrollWidth;
	
	var w = document.documentElement.offsetWidth;
	
	if (window.scrollMaxX)
		w += window.scrollMaxX;
	return w;
}

function getDocumentHeight() {
	if (document.body.scrollHeight)
		return document.body.scrollHeight;
	
	return document.documentElement.offsetHeight;
}


function getScrollXY() {

//http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
  var scrOfX = 0, scrOfY = 0;
  if( typeof( window.pageYOffset ) == 'number' ) {
    //Netscape compliant
    scrOfY = window.pageYOffset;
    scrOfX = window.pageXOffset;
  } else if( body && ( body.scrollLeft || body.scrollTop ) ) {
    //DOM compliant
    scrOfY = body.scrollTop;
    scrOfX = body.scrollLeft;
  } else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
    //IE6 standards compliant mode
    scrOfY = document.documentElement.scrollTop;
    scrOfX = document.documentElement.scrollLeft;
  }

	return [scrOfX,scrOfY];
}


function getScrollX(){
	//return the current x scroll - now that the canvas is abosolute not fixed for all but Safari 2, we return 0,except for Safari 2
	return 0;
}

function getScrollY(){
	//return the current y scroll - now that the canvas is abosolute not fixed for all but Safari 2, we return 0,except for Safari 2
	return 0;
}


function getElementProperty(theElement, whichStyle)
{
	//adapted from quirksmode.org
	try{
		////debug('getElementProperty > '+theElement+' > '+theElement.nodeName+' > '+whichStyle);
		return document.defaultView.getComputedStyle(theElement,null).getPropertyValue(whichStyle);
	}catch(e){
		//if(theElement.nodeName!='html')
			//debug('getElementProperty > '+theElement.nodeName+' > '+whichStyle+' > '+e);
		return null;
	}
}

function getBorderWidth(theElement, whichBorder){

	return getIntegerValue(getElementProperty(theElement, whichBorder+"-width"));
	
}


function getIntegerValue(theVal) {

	//these are for the values of border widths. All but IE return an integer value - here we translate based on what IE does
	if (theVal=='thin') return 2;
	if (theVal=='medium') return 4;
	if (theVal=='thick') return 6;
	
	var newVal= theVal.substr(0, theVal.length-2).valueOf();
	
	if (isNaN(newVal)) {
	
		return 0;
	}
	else 
	{
		return parseInt(newVal);
	}
}


function clearCanvas () {
	//delete it and reinsert it

	var canvas = getId("WCcanvas");

	canvas.removeEventListener('mousedown',hideCanvas,false);
	canvas.parentNode.removeChild(canvas);

}

function eraseCanvas (elementTop, elementLeft, elementWidth, elementHeight) {
	var canvas = getId("WCcanvas");
	var ctx = canvas.getContext("2d");
	ctx.clearRect(elementLeft, elementTop, elementWidth, elementHeight);
}

function draw(elementTop, elementLeft, elementWidth, elementHeight, fillColor) {
	var canvas = getId("WCcanvas");
	var ctx = canvas.getContext("2d");

	showCanvas();

	ctx.fillStyle = fillColor;
	ctx.fillRect (elementLeft, elementTop, elementWidth, elementHeight);
	
}


function xRayElement(theElement) {

	//debug('xRayElement > '+theElement+' > '+theElement.nodeName);
	if(!theElement) return;
	
	currentPatient=theElement;
	drawElementSkeleton(theElement);//
	//showElementDetails(theElement);
	//showWidthLabel(theElement);
	//showHeightLabel(theElement);
	//showTopLeftLabel(theElement);
	
	//if not in sight, scroll to it
	scrollToElement(theElement);
	
}

var currentPatient; //this is the element currently being xrayed

function xRayEvent(e) {
	//xray the element - called by the click handler
	
	//debug('xRayEvent > '+e.type+' > '+e.timeStamp);
	
	var e;
	if (!e) 
		return;//e = window.event;
    
	var tg = e.target;
	
    while (tg.nodeName == '#text'){//TODO
		//debug('xRayEvent  > #text > '+tg );
		tg = tg.parentNode;
	}
	
	if (tg.className=='XRAYclosebox'){ //todo
		////debug('xRayEvent  > XRAYclosebox' );
		uninstallXRAY();
		return false;
	}
	
	if (tg.className=='XRAYdetailedLink'){ //todo
		document.navigate("http://westciv.com");
		return true;
	}	

	xRayElement(tg);

	return false;
	
}

function drawElementSkeleton(theElement){//
	if(theElement.nodeName.search(/document/) > -1)
		return;

	//draws the 'skeleton' of the given element		
	
	//debug('drawElementSkeleton > '+theElement+' > '+theElement.nodeName);
	
	whereIs=getElementOffsetLocation(theElement);		
	////debug('drawElementSkeleton > ' +theElement+ ' > '+whereIs);
	
	elementTop=parseInt(whereIs[1]);
	elementLeft=parseInt(whereIs[0]);

	elementWidth=theElement.offsetWidth.valueOf();
	elementHeight=theElement.offsetHeight.valueOf(); 

	topPadding=getElementProperty(theElement,'padding-top');
	topPadding=getIntegerValue(topPadding);
			
	bottomPadding=getElementProperty(theElement,'padding-bottom');
	bottomPadding=getIntegerValue(bottomPadding);
	leftPadding=getElementProperty(theElement,'padding-left');
	leftPadding=getIntegerValue(leftPadding);
	rightPadding=getElementProperty(theElement,'padding-right');
	rightPadding=getIntegerValue(rightPadding);

	topMargin=getElementProperty(theElement,'margin-top');
	topMargin=getIntegerValue(topMargin);
	bottomMargin=getElementProperty(theElement,'margin-bottom');
	bottomMargin=getIntegerValue(bottomMargin);
	leftMargin=getElementProperty(theElement,'margin-left');
	leftMargin=getIntegerValue(leftMargin);
	rightMargin=getElementProperty(theElement,'margin-right');
	rightMargin=getIntegerValue(rightMargin);
	
	
	topBorder=getBorderWidth(theElement,'border-top');
	bottomBorder=getBorderWidth(theElement,'border-bottom');
	leftBorder=getBorderWidth(theElement,'border-left');
	rightBorder=getBorderWidth(theElement,'border-right');
	
	
	clearCanvas();
	insertCanvas();


	windowScrollX=getScrollX();
	windowScrollY=getScrollY();
	

	//add opaque background
	//semi opaque the whole window- needs to adjust canvas width and scale for window without scrollbars
	
	draw(0,0,getDocumentWidth() ,getDocumentHeight(), 'rgba(0,0,0,'+OPACITY+')');

	//clear the area where the element is
	eraseCanvas(elementTop-topMargin-topBorder-windowScrollY, elementLeft-leftMargin-leftBorder-windowScrollX, elementWidth+leftMargin+rightMargin+leftBorder+rightBorder, elementHeight+ topMargin + bottomMargin+bottomBorder+topBorder);


	//draw the content box
	eraseCanvas(elementTop+topPadding+topBorder-windowScrollY, elementLeft+leftPadding+leftBorder-windowScrollX, elementWidth-leftPadding-rightPadding-leftBorder-rightBorder, elementHeight-topPadding-bottomPadding-topBorder-bottomBorder);
}

function uninstallXRAY(){
	
	document.removeEventListener('click',xRayEvent,false);
	document.removeEventListener('mouseup',xRayEvent,false);
	window.removeEventListener('resize',windowResized,false);
	document.removeEventListener('keydown',keyPressed,false);	
	
	
	//clearCanvas();
	deleteNodeByID('XRAYHUD');
	deleteNodeByID('WCcanvas');
	
}

function installXRAY(e) {

	addCSS();
	insertCanvas();

	document.addEventListener('click',xRayEvent,false);
	document.addEventListener('mouseup',xRayEvent,false);
	unsafeWindow.addEventListener('resize',windowResized,false);
	document.addEventListener('keydown',keyPressed,false);	

}
	

function toggle(){
	if(on)
		try{uninstallXRAY();}catch(e){}
	else
		try{installXRAY();}catch(e){}
	on = !on;
}


function createElement(type, attrArray, evtListener, html)
{
	var node;
	try{
		node = document.getElementsByTagName('body')[0].createElement(type);
	}catch(e){
		node = document.createElement(type);
	}

	for (var attr in attrArray) if (attrArray.hasOwnProperty(attr)){
		node.setAttribute(attr, attrArray[attr]);
	}

	if(evtListener){
		var a = evtListener.split(' ');
		node.addEventListener(a[0], eval(a[1]), eval(a[2]));
	} 
 
	if(html) 
		node.innerHTML = html;
	
	return node;
}

function getId(id, parent){
	if(!parent)
		return document.getElementById(id);
	return parent.getElementById(id);	
}

function getTag(name, parent){
	if(!parent)
		return document.getElementsByTagName(name);
	return parent.getElementsByTagName(name);
}