1// fixed.js: fix fixed positioning and fixed backgrounds in IE/Win 2// version 1.8, 08-Aug-2003 3// written by Andrew Clover <and@doxdesk.com>, use freely 4 5/*@cc_on 6@if (@_win32 && @_jscript_version>4) 7 8var fixed_positions= new Array(); 9var fixed_backgrounds= new Array(); 10var fixed_viewport; 11 12// Initialisation. Called when the <body> tag arrives. Set up viewport so the 13// rest of the script knows we're going, and add a measurer div, used to detect 14// font size changes and measure image sizes for backgrounds later 15 16function fixed_init() { 17 fixed_viewport= (document.compatMode=='CSS1Compat') ? 18 document.documentElement : document.body; 19 var el= document.createElement('div'); 20 el.setAttribute('id', 'fixed-measure'); 21 el.style.position= 'absolute'; 22 el.style.top= '0'; el.style.left= '0'; 23 el.style.overflow= 'hidden'; el.style.visibility= 'hidden'; 24 el.style.fontSize= 'xx-large'; el.style.height= '5em'; 25 el.style.setExpression('width', 'fixed_measureFont()'); 26 document.body.insertBefore(el, document.body.firstChild); 27} 28 29// Binding. Called every time an element is added to the document, check it 30// for fixed features, if found add to our lists and set initial props 31 32function fixed_bind(el) { 33 var needLayout= false; 34 var tag= el.tagName.toLowerCase(); 35 var st= el.style; 36 var cst= el.currentStyle; 37 var anc; 38 39 // find fixed-position elements 40 if (cst.position=='fixed') { 41 needLayout= true; 42 fixed_positions[fixed_positions.length]= el; 43 // store original positioning as we'll overwrite it 44 st.position= 'absolute'; 45 st.fixedPLeft= cst.left; 46 st.fixedPTop= cst.top; 47 st.fixedPRight= cst.right; 48 st.fixedPBottom= cst.bottom; 49 st.fixedPWidth= fixed_parseLength(cst.width); 50 st.fixedPHeight= fixed_parseLength(cst.height); 51 // find element that will act as containing box, for convenience later 52 st.fixedCB= null; 53 for (anc= el; (anc= anc.parentElement).parentElement;) { 54 if (anc.currentStyle.position!='static') { 55 st.fixedCB= anc; 56 break; 57 } } 58 // detect nested fixed positioning (only ancestor need move) 59 st.fixedNest= false; 60 for (anc= el; anc= anc.parentElement;) { 61 if (anc.style.fixedNest!=null) 62 st.fixedNest= true; 63 break; 64 } 65 } 66 67 // find fixed-background elements (not body/html which IE already gets right) 68 if (cst.backgroundAttachment=='fixed' && tag!='body' && tag!='html') { 69 needLayout= true; 70 fixed_backgrounds[fixed_backgrounds.length]= el; 71 // get background offset, converting from keyword if necessary 72 st.fixedBLeft= fixed_parseLength(cst.backgroundPositionX); 73 st.fixedBTop= fixed_parseLength(cst.backgroundPositionY); 74 // if it's a non-zero %age, need to know size of image for layout 75 if (st.fixedBLeft[1]=='%' || st.fixedBTop[1]=='%') { 76 st.fixedBWidth= 0; st.fixedBHeight= 0; 77 fixed_measureBack(el); 78 } 79 } 80 if (needLayout) fixed_layout(); 81} 82 83// Layout. On every window or font size change, recalculate positioning 84 85// Request re-layout at next free moment 86var fixed_delaying= false; 87function fixed_delayout() { 88 if (fixed_delaying) return; 89 fixed_delaying= true; 90 window.setTimeout(fixed_layout, 0); 91} 92 93var fixed_ARBITRARY= 200; 94 95function fixed_layout() { 96 fixed_delaying= false; 97 if (!fixed_viewport) return; 98 var i, el, st, j, pr, tmp, A= 'auto'; 99 var cb, cbLeft, cbTop, cbRight, cbBottom, oLeft, oTop, oRight, oBottom; 100 var vpWidth=fixed_viewport.clientWidth, vpHeight=fixed_viewport.clientHeight; 101 102 // calculate initial position for fixed-position elements [black magic] 103 for (i= fixed_positions.length; i-->0;) { 104 el= fixed_positions[i]; st= el.style; 105 // find positioning of containing block 106 cb= st.fixedCB; if (!cb) cb= fixed_viewport; 107 cbLeft= fixed_pageLeft(cb); cbTop= fixed_pageTop(cb); 108 if (cb!=fixed_viewport) { cbLeft+= cb.clientLeft; cbTop+= cb.clientTop; } 109 cbRight= fixed_viewport.clientWidth-cbLeft-cb.clientWidth; 110 cbBottom= fixed_viewport.clientHeight-cbTop-cb.clientHeight; 111 // if size is in %, must recalculate relative to viewport 112 if (st.fixedPWidth[1]=='%') 113 st.width= Math.round(vpWidth*st.fixedPWidth[0]/100)+'px'; 114 if (st.fixedPHeight[1]=='%') 115 st.height= Math.round(vpHeight*st.fixedPHeight[0]/100)+'px'; 116 // find out offset values at max size, to account for margins 117 st.left= A; st.right= '0'; st.top= A; st.bottom= '0'; 118 oRight= el.offsetLeft+el.offsetWidth; oBottom= el.offsetTop+el.offsetHeight; 119 st.left= '0'; st.right= A; st.top= '0'; st.bottom= A; 120 oLeft= el.offsetLeft; oTop= el.offsetTop; 121 // use this to convert all edges to pixels 122 st.left= A; st.right= st.fixedPRight; 123 st.top= A; st.bottom= st.fixedPBottom; 124 oRight-= el.offsetLeft+el.offsetWidth; 125 oBottom-= el.offsetTop+el.offsetHeight; 126 st.left= st.fixedPLeft; st.top= st.fixedPTop; 127 oLeft= el.offsetLeft-oLeft; oTop= el.offsetTop-oTop; 128 // edge positioning fix 129 if (st.fixedPWidth[1]==A && st.fixedPLeft!=A && st.fixedPRight!=A) { 130 tmp= el.offsetLeft; st.left= A; st.width= fixed_ARBITRARY+'px'; 131 tmp= fixed_ARBITRARY+el.offsetLeft-tmp+cbLeft+cbRight; 132 st.left= st.fixedPLeft; st.width= ((tmp<1)?1:tmp)+'px'; 133 } 134 if (st.fixedPHeight[1]==A && st.fixedPTop!=A && st.fixedPBottom!=A) { 135 tmp= el.offsetTop; st.top= A; st.height= fixed_ARBITRARY+'px'; 136 tmp= fixed_ARBITRARY+el.offsetTop-tmp+cbTop+cbBottom; 137 st.top= st.fixedPTop; st.height= ((tmp<1)?1:tmp)+'px'; 138 } 139 // move all non-auto edges relative to the viewport 140 st.fixedCLeft= (st.fixedPLeft=='auto') ? oLeft : oLeft-cbLeft; 141 st.fixedCTop= (st.fixedPTop=='auto') ? oTop : oTop-cbTop; 142 st.fixedCRight= (st.fixedPRight=='auto') ? oRight : oRight-cbRight; 143 st.fixedCBottom= (st.fixedPBottom=='auto') ? oBottom : oBottom-cbBottom; 144 // remove left-positioning of right-positioned elements 145 if (st.fixedPLeft=='auto' && st.fixedPRight!='auto') st.fixedCLeft= 'auto'; 146 if (st.fixedPTop=='auto' && st.fixedPBottom!='auto') st.fixedCTop= 'auto'; 147 } 148 149 150 // calculate initial positioning of fixed backgrounds 151 for (i= fixed_backgrounds.length; i-->0;) { 152 el= fixed_backgrounds[i]; st= el.style; 153 tmp= st.fixedBImage; 154 if (tmp) { 155 if (tmp.readyState!='uninitialized') { 156 st.fixedBWidth= tmp.offsetWidth; 157 st.fixedBHeight= tmp.offsetHeight; 158 st.fixedBImage= window.undefined; 159 } 160 } 161 st.fixedBX= fixed_length(el, st.fixedBLeft, vpWidth-st.fixedBWidth); 162 st.fixedBY= fixed_length(el, st.fixedBTop, vpHeight-st.fixedBHeight); 163 } 164 165 // now call scroll() to set the positions from the values just calculated 166 fixed_scroll(); 167} 168 169// Scrolling. Offset fixed elements relative to viewport scrollness 170 171var fixed_lastX, fixed_lastY; 172var fixed_PATCHDELAY= 300; 173var fixed_patching= false; 174 175// callback function after a scroll, because incorrect scroll position is 176// often reported first go! 177function fixed_patch() { 178 fixed_patching= false; 179 var scrollX= fixed_viewport.scrollLeft, scrollY= fixed_viewport.scrollTop; 180 if (scrollX!=fixed_lastX && scrollY!=fixed_lastY) fixed_scroll(); 181} 182 183function fixed_scroll() { 184 if (!fixed_viewport) return; 185 var i, el, st, viewportX, viewportY; 186 var scrollX= fixed_viewport.scrollLeft, scrollY= fixed_viewport.scrollTop; 187 fixed_lastX= scrollX; fixed_lastY= scrollY; 188 189 // move non-nested fixed-position elements 190 for (i= fixed_positions.length; i-->0;) { 191 st= fixed_positions[i].style; 192 viewportX= (st.fixedNest) ? 0 : scrollX; 193 viewportY= (st.fixedNest) ? 0 : scrollY; 194 if (st.fixedCLeft!='auto') st.left= (st.fixedCLeft+viewportX)+'px'; 195 if (st.fixedCTop!='auto') st.top= (st.fixedCTop+viewportY)+'px'; 196 viewportX= (st.fixedCB==null || st.fixedCB==fixed_viewport) ? 0 : viewportX; 197 viewportY= (st.fixedCB==null || st.fixedCB==fixed_viewport) ? 0 : viewportY; 198 st.right= (st.fixedCRight-viewportX+1)+'px'; st.right= (st.fixedCRight-viewportX)+'px'; 199 st.bottom= (st.fixedCBottom-viewportY+1)+'px'; st.bottom= (st.fixedCBottom-viewportY)+'px'; 200 } 201 202 // align fixed backgrounds to viewport 203 for (i= fixed_backgrounds.length; i-->0;) { 204 el= fixed_backgrounds[i]; st= el.style; 205 viewportX= scrollX; 206 viewportY= scrollY; 207 while (el.offsetParent) { 208 viewportX-= el.offsetLeft+el.clientLeft; 209 viewportY-= el.offsetTop +el.clientTop; 210 el= el.offsetParent; 211 } 212 st.backgroundPositionX= (st.fixedBX+viewportX)+'px'; 213 st.backgroundPositionY= (st.fixedBY+viewportY)+'px'; 214 } 215 216 // call back again in a tic 217 if (!fixed_patching) { 218 fixed_patching= true; 219 window.setTimeout(fixed_patch, fixed_PATCHDELAY); 220 } 221} 222 223// Measurement. Load bg-image into an invisible element on the page, when 224// loaded write the width/height to an element's style for layout use; detect 225// when font size changes 226 227function fixed_measureBack(el) { 228 var measure= document.getElementById('fixed-measure'); 229 var img= document.createElement('img'); 230 img.setAttribute('src', fixed_parseURL(el.currentStyle.backgroundImage)); 231 measure.appendChild(img); 232 el.style.fixedBImage= img; 233 if (img.readyState=='uninitialized') 234 img.attachEvent('onreadystatechange', fixed_measureBackImage_ready); 235} 236 237function fixed_measureBackImage_ready() { 238 var img= event.srcElement; 239 if (img && img.readyState!='uninitialized') { 240 img.detachEvent('onreadystatechange', fixed_measureBackImage_ready); 241 fixed_layout(); 242 } 243} 244 245var fixed_fontsize= 0; 246function fixed_measureFont() { 247 var fs= document.getElementById('fixed-measure').offsetHeight; 248 if (fixed_fontsize!=fs && fixed_fontsize!=0) 249 fixed_delayout(); 250 fixed_fontsize= fs; 251 return '5em'; 252} 253 254// Utility. General-purpose functions 255 256// parse url() to get value inside 257 258function fixed_parseURL(v) { 259 v= v.substring(4, v.length-1); 260 if (v.charAt(0)=='"' && v.charAt(v.length-1)=='"' || 261 v.charAt(0)=="'" && v.charAt(v.length-1)=="'") 262 return v.substring(1, v.length-1); 263 else return v; 264} 265 266// parse length or auto or background-position keyword into number and unit 267 268var fixed_numberChars= '+-0123456789.'; 269var fixed_ZERO= new Array(0, 'px'); 270var fixed_50PC= new Array(50, '%'); 271var fixed_100PC= new Array(100, '%'); 272var fixed_AUTO= new Array(0, 'auto'); 273 274function fixed_parseLength(v) { 275 var num, i; 276 if (v=='left' || v=='top') return fixed_ZERO; 277 if (v=='right' || v=='bottom') return fixed_100PC; 278 if (v=='center') return fixed_50PC; 279 if (v=='auto') return fixed_AUTO; 280 i= 0; 281 while (i<v.length && fixed_numberChars.indexOf(v.charAt(i))!=-1) 282 i++; 283 num= parseFloat(v.substring(0, i)); 284 if (num==0) return fixed_ZERO; 285 else return new Array(num, v.substring(i)); 286} 287 288// convert parsed (number, unit) into a number of pixels 289 290function fixed_length(el, l, full) { 291 var tmp, x; 292 if (l[1]=='px') return l[0]; 293 if (l[1]=='%') return Math.round(full*l[0]/100); 294 // other units - measure by setting position; this is rather inefficient 295 // but then these units are used for background-position so seldom... 296 tmp= el.currentStyle.left; 297 el.style.left= '0'; 298 x= el.offsetLeft; 299 el.style.left= l[0]+l[1]; 300 x= el.offsetLeft-x; 301 el.style.left= tmp; 302 return x; 303} 304 305// convert stupid IE offsetLeft/Top to page-relative values 306 307function fixed_pageLeft(el) { 308 var v= 0; 309 while (el.offsetParent) { 310 v+= el.offsetLeft; 311 el= el.offsetParent; 312 } 313 return v; 314} 315function fixed_pageTop(el) { 316 var v= 0; 317 while (el.offsetParent) { 318 v+= el.offsetTop; 319 el= el.offsetParent; 320 } 321 return v; 322} 323 324// Scanning. Check document every so often until it has finished loading. Do 325// nothing until <body> arrives, then call main init. Pass any new elements 326// found on each scan to be bound 327 328var fixed_SCANDELAY= 500; 329 330function fixed_scan() { 331 if (!document.body) return; 332 if (!fixed_viewport) fixed_init(); 333 var el; 334 for (var i= 0; i<document.all.length; i++) { 335 el= document.all[i]; 336 if (!el.fixed_bound) { 337 el.fixed_bound= true; 338 fixed_bind(el); 339 } } 340} 341 342var fixed_scanner; 343function fixed_stop() { 344 window.clearInterval(fixed_scanner); 345 fixed_scan(); 346} 347 348fixed_scan(); 349fixed_scanner= window.setInterval(fixed_scan, fixed_SCANDELAY); 350window.attachEvent('onload', fixed_stop); 351window.attachEvent('onresize', fixed_delayout); 352window.attachEvent('onscroll', fixed_scroll); 353 354@end @*/ 355