├── .gitignore ├── .travis.yml ├── History.md ├── Makefile ├── README.md ├── component.json ├── index.js ├── package.json └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | env: 5 | global: 6 | - secure: MDVqz/3OPZtr7ru8sEhLAnU4ritpfCFNSETfbEK54qyX0TaU4/HN6+t/LNnLApQjjXorDzGCEEt6o1KKHzte7c9NUwtu/HsN3zc4IpSJb3XmlKJTYYx+vNzUhiMbMKMi+CTOf40mVzbwOCZc5npIYDUTHZs1Fa/ng7wmZ8iHRcM= 7 | - secure: Yk0/ca64//PjUP04sKD6+IXgTSKa8/udppVQCy15PmiY5hzP4RRBzC4CX2FlzKRuirr7QnahDI7Sis6ICZSGZRbw2q25GulKU0VlayhQENuzDTtGwopCYApQRI+9gNyRCSQ3rn8gGSELcdjt6khaHZc3wuKHPvdIFiFSjsN9Wlk= 8 | matrix: 9 | - BROWSER_NAME=chrome BROWSER_VERSION=latest 10 | - BROWSER_NAME=chrome BROWSER_VERSION=35 11 | - BROWSER_NAME=chrome BROWSER_VERSION=34 12 | - BROWSER_NAME=chrome BROWSER_VERSION=33 13 | - BROWSER_NAME=chrome BROWSER_VERSION=32 14 | - BROWSER_NAME=chrome BROWSER_VERSION=31 15 | - BROWSER_NAME=chrome BROWSER_VERSION=30 16 | - BROWSER_NAME=firefox BROWSER_VERSION=latest 17 | - BROWSER_NAME=firefox BROWSER_VERSION=30 18 | - BROWSER_NAME=firefox BROWSER_VERSION=29 19 | - BROWSER_NAME=firefox BROWSER_VERSION=28 20 | - BROWSER_NAME=firefox BROWSER_VERSION=27 21 | - BROWSER_NAME=firefox BROWSER_VERSION=26 22 | - BROWSER_NAME=firefox BROWSER_VERSION=25 23 | - BROWSER_NAME=opera BROWSER_VERSION=latest 24 | - BROWSER_NAME=opera BROWSER_VERSION=11 25 | - BROWSER_NAME=safari BROWSER_VERSION=latest 26 | - BROWSER_NAME=safari BROWSER_VERSION=7 27 | - BROWSER_NAME=safari BROWSER_VERSION=6 28 | - BROWSER_NAME=ie BROWSER_VERSION=11 29 | - BROWSER_NAME=ie BROWSER_VERSION=10 30 | - BROWSER_NAME=ie BROWSER_VERSION=9 31 | - BROWSER_NAME=iphone BROWSER_VERSION=7.1 32 | - BROWSER_NAME=iphone BROWSER_VERSION=7.0 33 | - BROWSER_NAME=iphone BROWSER_VERSION=6.1 34 | - BROWSER_NAME=iphone BROWSER_VERSION=6.0 35 | - BROWSER_NAME=iphone BROWSER_VERSION=5.0 36 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.5 / 2015-01-26 3 | ================== 4 | 5 | * update 'get-document' to v1 6 | 7 | 1.0.4 / 2015-01-20 8 | ================== 9 | 10 | * index: do the 0's check on *all* Range instances 11 | * README++ 12 | 13 | 1.0.3 / 2014-12-30 14 | ================== 15 | 16 | * remove "debug" dependency (#3, @dfcreative) 17 | 18 | 1.0.2 / 2014-12-17 19 | ================== 20 | 21 | * Adding visionmedia/debug dep to fix for component/duo (#1, @dominicbarnes) 22 | 23 | 1.0.1 / 2014-12-16 24 | ================== 25 | 26 | * add `component.json` file 27 | * enable travis CI + saucelabs cloud testing 28 | * index: add link to SO thread explaining TextNode fix 29 | 30 | 1.0.0 / 2014-12-15 31 | ================== 32 | 33 | * initial commit 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # get Makefile directory name: http://stackoverflow.com/a/5982798/376773 3 | THIS_MAKEFILE_PATH:=$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) 4 | THIS_DIR:=$(shell cd $(dir $(THIS_MAKEFILE_PATH));pwd) 5 | 6 | # BIN directory 7 | BIN := $(THIS_DIR)/node_modules/.bin 8 | 9 | # applications 10 | NODE ?= node 11 | ZUUL ?= $(NODE) $(BIN)/zuul 12 | 13 | test: 14 | @if [ "x$(BROWSER_PLATFORM)" = "x" ]; then \ 15 | $(ZUUL) \ 16 | --ui mocha-bdd \ 17 | --browser-name $(BROWSER_NAME) \ 18 | --browser-version $(BROWSER_VERSION) \ 19 | test/*.js; \ 20 | else \ 21 | $(ZUUL) \ 22 | --ui mocha-bdd \ 23 | --browser-name $(BROWSER_NAME) \ 24 | --browser-version $(BROWSER_VERSION) \ 25 | --browser-platform "$(BROWSER_PLATFORM)" \ 26 | test/*.js; \ 27 | fi 28 | 29 | .PHONY: test 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bounding-client-rect 2 | ==================== 3 | ### Cross-browser `getBoundingClientRect()` for all Node types 4 | 5 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/bounding-client-rect.svg)](https://saucelabs.com/u/bounding-client-rect) 6 | 7 | [![Build Status](https://travis-ci.org/webmodules/bounding-client-rect.svg?branch=master)](https://travis-ci.org/webmodules/bounding-client-rect) 8 | 9 | [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect) 10 | that works on DOM 11 | [`Elements`](https://developer.mozilla.org/en-US/docs/Web/API/Element), 12 | [`Ranges`](https://developer.mozilla.org/en-US/docs/Web/API/Range), and 13 | [`TextNodes`](https://developer.mozilla.org/en-US/docs/Web/API/Text). 14 | 15 | 16 | Installation 17 | ------------ 18 | 19 | ``` bash 20 | $ npm install bounding-client-rect 21 | ``` 22 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bounding-client-rect", 3 | "repo": "webmodules/bounding-client-rect", 4 | "version": "1.0.5", 5 | "description": "Cross-browser `getBoundingClientRect()` for all Node types", 6 | "keywords": [ 7 | "browser", 8 | "bounding", 9 | "client", 10 | "rect", 11 | "getBoundingClientRect", 12 | "TextRectangle", 13 | "TextNode", 14 | "Node", 15 | "Range", 16 | "HTMLElement" 17 | ], 18 | "main": "index.js", 19 | "author": "Nathan Rajlich ", 20 | "license": "MIT", 21 | "scripts": [ 22 | "index.js" 23 | ], 24 | "dependencies": { 25 | "webmodules/get-document": "1.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var getDocument = require('get-document'); 7 | 8 | /** 9 | * Module exports. 10 | */ 11 | 12 | module.exports = getBoundingClientRect; 13 | 14 | /** 15 | * Returns the "bounding client rectangle" of the given `TextNode`, 16 | * `HTMLElement`, or `Range`. 17 | * 18 | * @param {Node} node 19 | * @return {TextRectangle} 20 | * @public 21 | */ 22 | 23 | function getBoundingClientRect (node) { 24 | var rect = null; 25 | var doc = getDocument(node); 26 | 27 | if (node.nodeType === 3 /* TEXT_NODE */) { 28 | // see: http://stackoverflow.com/a/6966613/376773 29 | var range = doc.createRange(); 30 | range.selectNodeContents(node); 31 | node = range; 32 | } 33 | 34 | if ('function' === typeof node.getBoundingClientRect) { 35 | rect = node.getBoundingClientRect(); 36 | 37 | if (node.startContainer && rect.left === 0 && rect.top === 0) { 38 | // Range instances sometimes report all `0`s 39 | // see: http://stackoverflow.com/a/6847328/376773 40 | var span = doc.createElement('span'); 41 | 42 | // Ensure span has dimensions and position by 43 | // adding a zero-width space character 44 | span.appendChild(doc.createTextNode('\u200b')); 45 | node.insertNode(span); 46 | rect = span.getBoundingClientRect(); 47 | 48 | // Remove temp SPAN and glue any broken text nodes back together 49 | var spanParent = span.parentNode; 50 | spanParent.removeChild(span); 51 | spanParent.normalize(); 52 | } 53 | 54 | } 55 | 56 | return rect; 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bounding-client-rect", 3 | "version": "1.0.5", 4 | "description": "Cross-browser `getBoundingClientRect()` for all Node types", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "make test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/webmodules/bounding-client-rect.git" 12 | }, 13 | "keywords": [ 14 | "browser", 15 | "bounding", 16 | "client", 17 | "rect", 18 | "getBoundingClientRect", 19 | "TextRectangle", 20 | "TextNode", 21 | "Node", 22 | "Range", 23 | "HTMLElement" 24 | ], 25 | "author": "Nathan Rajlich ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/webmodules/bounding-client-rect/issues" 29 | }, 30 | "homepage": "https://github.com/webmodules/bounding-client-rect", 31 | "dependencies": { 32 | "get-document": "1" 33 | }, 34 | "devDependencies": { 35 | "zuul": "2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var getBoundingClientRect = require('../'); 4 | 5 | describe('getBoundingClientRect', function () { 6 | 7 | var div = document.createElement('div'); 8 | div.innerHTML = '

hello

'; 9 | div.setAttribute('contenteditable', 'true'); 10 | document.body.appendChild(div); 11 | 12 | after(function () { 13 | document.body.removeChild(div); 14 | }); 15 | 16 | function assertIsTextRectangle (rect) { 17 | // see: 18 | // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIDOMClientRect 19 | assert.equal('number', typeof rect.bottom); 20 | assert.equal('number', typeof rect.height); 21 | assert.equal('number', typeof rect.left); 22 | assert.equal('number', typeof rect.right); 23 | assert.equal('number', typeof rect.top); 24 | assert.equal('number', typeof rect.width); 25 | } 26 | 27 | it('should return a client rect for a P HTMLElement', function () { 28 | var p = div.firstChild; 29 | assert.equal(1 /*Node.ELEMENT_NODE*/, p.nodeType); 30 | assert.equal('P', p.nodeName); 31 | 32 | var rect = getBoundingClientRect(p); 33 | 34 | assertIsTextRectangle(rect); 35 | }); 36 | 37 | it('should return a client rect for a collapsed Range', function () { 38 | var range = document.createRange(); 39 | range.setStart(div.firstChild.firstChild, 1); 40 | range.setEnd(div.firstChild.firstChild, 1); 41 | assert(range.collapsed); 42 | 43 | var rect = getBoundingClientRect(range); 44 | 45 | assertIsTextRectangle(rect); 46 | }); 47 | 48 | it('should return a client rect for a non-collapsed Range', function () { 49 | var range = document.createRange(); 50 | range.setStart(div.firstChild.firstChild, 1); 51 | range.setEnd(div.firstChild.firstChild, 4); 52 | assert(!range.collapsed); 53 | 54 | var rect = getBoundingClientRect(range); 55 | 56 | assertIsTextRectangle(rect); 57 | }); 58 | 59 | it('should return a client rect for a TextNode', function () { 60 | var textNode = div.firstChild.firstChild; 61 | assert.equal(3 /*Node.TEXT_NODE*/, textNode.nodeType); 62 | assert.equal('hello', textNode.nodeValue); 63 | 64 | var rect = getBoundingClientRect(textNode); 65 | 66 | assertIsTextRectangle(rect); 67 | }); 68 | 69 | }); 70 | --------------------------------------------------------------------------------