<meta charset="utf-8" />
<title> scrollIntoView polyfill of the CSSOM-view proposal </title>
<style>
html {
height: 300%;
width: 300%;
}
div#some {
position: absolute;
background: black;
width: 2px;
height: 2px;
}
iframe {
position: absolute;
width: 40px;
height: 40px;
border: 0;
}
div.success, div.fail {
border-radius: 3px 0px 3px 4px;
max-width: 12cm;
padding: 7px;
margin: 5px;
}
div.success {
border-left: green 2px solid;
border-bottom: green 1px solid;
background: linear-gradient(35deg, green, #af6 17px, white 20px);
background: linear-gradient(35deg, green, #af6 17px, white 20px);
background: linear-gradient(35deg, green, #af6 17px, white 20px);
box-shadow: inset 3px -3px 3px -3px #4a0;
}
div.fail {
border-left: red 2px solid;
border-bottom: red 1px solid;
background: linear-gradient(35deg, red, #fa6 17px, white 20px);
background: linear-gradient(35deg, red, #fa6 17px, white 20px);
background: linear-gradient(35deg, red, #fa6 17px, white 20px);
box-shadow: inset 3px -3px 3px -3px #a40;
}
</style>
<div id="some"></div>
<div id="results"></div>
<iframe id="frame" src="http://jsbin.com/3/ojoguy/3"></iframe>
////////////////////////////////////////////////////////////////////////////////
// Author: Thaddee Tyl 2012.
// The following work is under CC0.
function scrollIntoView (options) {
var window = this.ownerDocument.defaultView;
// Traditional scrollIntoView.
if (options === undefined || options === true
|| options === false) {
this.scrollIntoView(options);
return;
}
// Fetch positional information.
//
// The following are always from the {top, bottom, left, right}
// of the viewport, to the {top, …} of the box.
// Think of them as geometrical vectors, it helps.
// The axes are directed downwards and towards the right.
var rect = this.getBoundingClientRect(),
topToBottom = rect.bottom,
bottomToTop = rect.top - window.innerHeight,
leftToRight = rect.right,
rightToLeft = rect.left - window.innerWidth,
xAllowed = true, // We allow one translation on the x axis,
yAllowed = true; // and one on the y axis.
// Read options.
//
// We have the following options:
// - float vertical = 0.5 (from 0 to 1).
// - float horizontal = 0.0 (from 0 to 1).
// - boolean notIfViewed = true
if (options.vertical === undefined) options.vertical = 0.5;
if (options.horizontal === undefined) options.horizontal = 0.5;
if (options.evenIfViewed === undefined) options.evenIfViewed = false;
// Whatever `horizontal` and `vertical` are,
// the behavior is the same if the box is (even partially) visible.
if (topToBottom > 0 && topToBottom <= this.offsetHeight) {
if (yAllowed) {
window.scrollBy(0, topToBottom - this.offsetHeight);
yAllowed = false;
}
} else
if (bottomToTop < 0 && bottomToTop >= -this.offsetHeight) {
if (yAllowed) {
window.scrollBy(0, bottomToTop + this.offsetHeight);
yAllowed = false;
}
}
if (leftToRight > 0 && leftToRight <= this.offsetWidth) {
if (xAllowed) {
window.scrollBy(leftToRight - this.offsetWidth, 0);
xAllowed = false;
}
} else
if (rightToLeft < 0 && rightToLeft >= -this.offsetWidth) {
if (xAllowed) {
window.scrollBy(rightToLeft + this.offsetWidth, 0);
xAllowed = false;
}
}
// If we want it positioned in the viewport,
// and the box is completely hidden,
// then we position it explicitly.
if (yAllowed && (options.evenIfViewed? true:
(topToBottom <= 0 || bottomToTop >= 0))) {
window.scroll(window.scrollX,
window.scrollY + rect.top
- (window.innerHeight - this.offsetHeight) * options.vertical);
}
if (xAllowed && (options.evenIfViewed? true:
(leftToRight <= 0 || rightToLeft <= 0))) {
window.scroll(window.scrollX + rect.left
- (window.innerWidth - this.offsetWidth) * options.horizontal,
window.scrollY);
}
if (window.parent !== window) {
// We are inside a scrollable element.
var frame = window.frameElement;
scrollIntoView.call(frame, options);
}
}
// Hook the polyfill.
Element.prototype._scrollIntoView = scrollIntoView;
// Tests.
function asserteq (a, b, test) {
var bool = a === b,
box = document.createElement('div');
box.textContent = test;
if (!bool) console.error('Assertion error', test || '',
a, 'is not', b);
if (test) {
if (!bool) box.className = 'fail';
else box.className = 'success';
}
document.getElementById('results').appendChild(box);
}
(function () {
var id = document.getElementById.bind(document),
some = id('some');
some.style.top = innerHeight + 'px';
some.style.left = innerWidth + 'px';
// The tests start with a black 2x2 pixels square below bottom right.
// Do not resize the window during the tests.
// In comments on the right, I am talking about that square box.
scroll(innerWidth / 2, innerHeight + 2); // Just above the viewport.
some._scrollIntoView({});
asserteq(scrollY, Math.floor(innerHeight / 2) + 1,
'Test 1: element hidden above.');
scroll(innerWidth / 2, innerHeight + 2); // Just above the viewport.
some._scrollIntoView({vertical:0});
asserteq(scrollY, innerHeight,
'Test 2: element hidden above, vertical=0.');
scroll(innerWidth / 2, innerHeight + 2); // Just above the viewport.
some._scrollIntoView({vertical:1});
asserteq(scrollY, 2,
'Test 3: element hidden above, vertical=1.');
scroll(innerWidth / 2, innerHeight + 2); // Just above the viewport.
some._scrollIntoView({horizontal:0});
asserteq(scrollX, innerWidth,
'Test 4: element hidden above, horizontal=0.');
scroll(innerWidth / 2, innerHeight + 2); // Just above the viewport.
some._scrollIntoView({horizontal:1});
asserteq(scrollX, 2,
'Test 5: element hidden above, horizontal=1.');
scroll(innerWidth / 2, innerHeight + 1); // Just along the viewport.
some._scrollIntoView({});
asserteq(scrollY, innerHeight,
'Test 6: element partially visible above.');
scroll(innerWidth / 2, innerHeight - 1); // In the viewport.
some._scrollIntoView({vertical:0});
asserteq(scrollY, innerHeight - 1,
'Test 7: element visible above.');
scroll(innerWidth / 2, innerHeight - 1); // In the viewport.
some._scrollIntoView({evenIfViewed:true, vertical:0});
asserteq(scrollY, innerHeight,
'Test 8: element visible above, evenIfViewed=true.');
// Huge, window-sized box.
some.style.width = innerWidth + 'px';
some.style.height = innerHeight + 'px';
// Viewport is at the bottom right of the box.
scroll(2 * innerWidth, 2 * innerHeight);
some._scrollIntoView({});
asserteq(scrollX, innerWidth,
'Test 9: element bigger than viewport (x)');
asserteq(scrollY, innerHeight,
'Test 10: element bigger than viewport (y)');
// Iframe tests.
some.style.height = '0px';
scroll(0, 0);
var frame = id('frame'),
fwindow = frame.contentWindow;
frame.style.top = window.innerHeight + 'px';
frame.style.left = window.innerWidth + 'px';
fwindow.addEventListener('load', function frameLoad() {
var some = fwindow.document.getElementById('some');
some._scrollIntoView({});
asserteq(window.scrollX, Math.floor(window.innerWidth / 2) + 20,
'Test 11: scrolling from an iframe (x)');
asserteq(window.scrollY, Math.floor(window.innerHeight / 2) + 20,
'Test 12: scrolling from an iframe (y)');
asserteq(fwindow.scrollX, Math.floor(fwindow.innerWidth / 2) + 1,
'Test 13: scrolling inside an iframe (x)');
asserteq(fwindow.scrollY, Math.floor(fwindow.innerHeight / 2) + 1,
'Test 14: scrolling inside an iframe (y)');
scroll(0, 0);
}, false);
}());
Output
300px
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |