1 2// 3// Gibt die CSS-Eigenschaften des übergebenen Elements zurück 4// 5function getStyleValue (element, cssProperty) 6{ 7 var value = ""; 8 if (window.getComputedStyle) 9 { 10 var styles = window.getComputedStyle(element, null); 11 value = window.getComputedStyle(element, null)[cssProperty]; 12 } 13 else if (element.currentStyle) 14 { 15 value = element.currentStyle[cssProperty]; 16 } 17 return value; 18} 19 20function getElementHeightValue(element) 21{ 22 if (!element) 23 return 0; 24 25 // Der einfache Weg: 26 if(element.getBoundingClientRect) 27 { 28 return element.getBoundingClientRect().bottom - element.getBoundingClientRect().top; 29 } 30 else 31 { 32 return getStyleMeasureValue(getStyleValue(element, "height")); 33 } 34} 35 36function getElementWidthValue(element) 37{ 38 if (!element) 39 return 0; 40 41 // Der einfache Weg: 42 if(element.getBoundingClientRect) 43 { 44 return element.getBoundingClientRect().right - element.getBoundingClientRect().left; 45 } 46 else 47 { 48 return getStyleMeasureValue(getStyleValue(element, "width")); 49 } 50} 51 52function getElementLeftValue(element) 53{ 54 if (!element) 55 return 0; 56 57 // Der einfache Weg: 58 if(element.getBoundingClientRect) 59 { 60 // Die Methode orientiert sich an der Position im Browser, nicht im 61 // Dokument. 62 // Darum den Scrollwert dazurechnen: 63 return element.getBoundingClientRect().left+getScrollPosition().x; 64 } 65 else 66 { 67 return getStyleMeasureValue(getStyleValue(element, "left")); 68 } 69} 70 71function getElementTopValue(element) 72{ 73 if (!element) 74 return 0; 75 76 // Der einfache Weg: 77 if(element.getBoundingClientRect) 78 { 79 // Die Methode orientiert sich an der Position im Browser, nicht im 80 // Dokument. 81 // Darum den Scrollwert dazurechnen: 82 return element.getBoundingClientRect().top+getScrollPosition().y; 83 } 84 else 85 { 86 return getStyleMeasureValue(getStyleValue(element, "top")); 87 } 88} 89 90function getElementRightValue(element) 91{ 92 if (!element) 93 return 0; 94 95 // Der einfache Weg: 96 if(element.getBoundingClientRect) 97 { 98 // Die Methode orientiert sich an der Position im Browser, nicht im 99 // Dokument. 100 // Darum den Scrollwert dazurechnen: 101 return element.getBoundingClientRect().right + getScrollPosition().x; 102 } 103 else 104 { 105 return getStyleMeasureValue(getStyleValue(element, "right")); 106 } 107} 108 109function getElementBottomValue(element) 110{ 111 if (!element) 112 return 0; 113 114 // Der einfache Weg: 115 if(element.getBoundingClientRect) 116 { 117 // Die Methode orientiert sich an der Position im Browser, nicht im 118 // Dokument. 119 // Darum den Scrollwert dazurechnen: 120 return element.getBoundingClientRect().bottom + getScrollPosition().y; 121 } 122 else 123 { 124 return getStyleMeasureValue(getStyleValue(element, "bottom")); 125 } 126} 127 128function getStyleMeasureValue(measureValue) 129{ 130 var ergebnis = 0; 131 132 if (measureValue == null || measureValue == "") 133 return 0; 134 135 if (isNaN(measureValue)) 136 { 137 var numberEnd = false; 138 var numberString = ""; 139 for (var i = 0; i < measureValue.length && !numberEnd; i++) 140 { 141 if (!isNaN(measureValue[i]) || measureValue[i] == '.' || measureValue[i] == '-' || measureValue[i] == '+') 142 numberString += measureValue[i]; 143 else 144 numberEnd = true; 145 } 146 ergebnis = Number(numberString); 147 } 148 else 149 ergebnis = measureValue; 150 151 return ergebnis; 152} 153 154/** 155 * Funktion ermittelt die Einheit einer CSS-Größenangabe. 156 * Aus "100px" wird beispielsweise "px". Bei Spezialangaben, wie 157 * auto, wird ein Leerstring zurückgegeben, da es sich nicht um eine Einheit handelt. 158 * 159 * @param {Object} measureValue Größenangabe als Zeichenkette. 160 */ 161function getStyleMeasureUnit(measureValue) 162{ 163 var ergebnis = ""; 164 165 if (isNaN(measureValue)) 166 { 167 var unitEnd = false; 168 for (var i = measureValue.length - 1; i >=0 && !unitEnd; i--) 169 { 170 if (!isNaN(measureValue[i]) || measureValue[i] == '.' || measureValue[i] == '-' || measureValue[i] == '+') 171 unitEnd = true; 172 else 173 ergebnis = measureValue[i] + ergebnis; 174 } 175 if (!unitEnd) // Wenn wir bei der Suche nie auf eine Zahl gestoßen sind, bedeutet das, 176 ergebnis = ""; // dass wir z.B. eine Angabe wie auto haben, dann gibts auch keine Einheit. 177 } 178 179 return ergebnis; 180} 181 182/** 183 * Gibt die Breite des gesamten Dokuments zurück. 184 * Wenn alle Standardfunktionen auf dem documentElement
185 * nicht greifen, wird die Breite des Body-Elements zurückgegeben. 186 */ 187var HRGetDocumentWidth = function() 188{ 189 var widthValue = -1; 190 if (document.documentElement.scrollWidth) 191 { 192 widthValue = document.documentElement.scrollWidth; 193 } 194 if (document.documentElement.offsetWidth) 195 { 196 widthValue = Math.max(widthValue, document.documentElement.offsetWidth); 197 } 198 if (document.documentElement.clientWidth) 199 { 200 widthValue = Math.max(widthValue, document.documentElement.clientWidth); 201 } 202 if (widthValue < 0) 203 { 204 widthValue = HRGetBodyWidth(); 205 } 206 return Math.ceil(widthValue); 207} 208 209 210/** 211 * Gibt die Höhe des gesamten Dokuments zurück. 212 * Wenn alle Standardfunktionen auf dem documentElement 213 * nicht greifen, wird die Höhe des Body-Elements zurückgegeben. 214 * Liefert ggfs. für iFrame-Resizer zu große Werte, da die Höhe 215 * auch durch die Größe des iFrames beeinflusst wird. 216 */ 217var HRGetDocumentHeight = function() 218{ 219 var heightValue = -1; 220 if (document.documentElement.scrollHeight) 221 { 222 heightValue = document.documentElement.scrollHeight; 223 } 224 if (document.documentElement.offsetHeight) 225 { 226 heightValue = Math.max(heightValue, document.documentElement.offsetHeight); 227 } 228 if (document.documentElement.clientHeight) 229 { 230 heightValue = Math.max(heightValue, document.documentElement.clientHeight); 231 } 232 if (heightValue < 0) 233 { 234 heightValue = HRGetBodyHeight(); 235 } 236 else 237 { 238 /* 239 * Wenn die Funktion genutzt wird, um eine automatische Höhenanpassung bei IFrames 240 * zu realisieren, ist der ermittelter Wert immer um 1 zu hoch. Sollte das 241 * zu negativen Effekten in anderen Einsatzszenarien führen, muss diese Lösung 242 * noch einmal überdacht werden. 243 * 21.03.2016 mhu 244 */ 245 heightValue = heightValue - 1; 246 } 247 248 return Math.ceil(heightValue); 249} 250 251/** 252 * Methode ermittelt die Breite, die das Element braucht (also inklusive Margins). 253 * Wird null als Element übergeben, wird die benötigte Breite für das ganze Dokument 254 * zurückgegeben. Anders als bei HRGetDocumentWidth() wird der Wert nicht von 255 * der Größe des Brwoserfensters oder eines umgebenden iFrames beeinflusst. 256 * 257 * @param element, dessen benötigte Breite ermittelt werden soll oder null, 258 * wenn die Breite für das ganze Dokument ermittelt werden soll. 259 */ 260var HRGetUsedWidth = function(element) 261{ 262 var widthValue = -1; 263 if (element == null) 264 { 265 // 266 // Ermittlung für das gesamte Document. 267 // Wir ermitteln dazu den größten Right + margin-right-Wert 268 // 269 try 270 { 271 var maxRight = 0; 272 var alleElemente = document.querySelectorAll('body *'); 273 for (var i = 0; i < alleElemente.length; i++) 274 { 275 maxRight = Math.max(maxRight, alleElemente[i].getBoundingClientRect().right + getScrollPosition().x + getStyleMeasureValue(getStyleValue(alleElemente[i], "margin-right"))); 276 } 277 widthValue = maxRight; 278 } 279 catch (except) 280 { 281 } 282 // 283 // Bei Fehlern wird die gut abgehangene HRGetDocumentWidth() als Fallback verwendet. 284 // 285 try 286 { 287 if (widthValue < 0) 288 { 289 widthValue = HRGetDocumentWidth(); 290 } 291 } 292 catch (except) 293 { 294 } 295 } 296 else 297 { 298 widthValue = getElementWidthValue(element) + getStyleMeasureValue(getStyleValue(element, "margin-left")) + getStyleMeasureValue(getStyleValue(element, "margin-right")); 299 } 300 return widthValue; 301} 302 303/** 304 * Methode ermittelt die Höhe, die das Element braucht (also inklusive Margins). 305 * Wird null als Element übergeben, wird die benötigte Höhe für das ganze Dokument 306 * zurückgegeben. Anders als bei HRGetDocumentHeight() wird der Wert nicht von 307 * der Größe des Brwoserfensters oder eines umgebenden iFrames beeinflusst. 308 * 309 * @param element, dessen benötigte Höhe ermittelt werden soll oder null, 310 * wenn die Höhe für das ganze Dokument ermittelt werden soll. 311 */ 312var HRGetUsedHeight = function(element) 313{ 314 var heightValue = -1; 315 if (element == null) 316 { 317 // 318 // Ermittlung für das gesamte Document.
319 // Wir ermitteln dazu den größten Bottom + margin-bottom-Wert 320 // 321 try 322 { 323 var maxBottom = 0; 324 var alleElemente = document.querySelectorAll('body *'); 325 for (var i = 0; i < alleElemente.length; i++) 326 { 327 if (alleElemente[i] != null && alleElemente[i].nodeName && alleElemente[i].nodeName.toLowerCase() != 'option') 328 { 329 maxBottom = Math.max(maxBottom, alleElemente[i].getBoundingClientRect().bottom + getScrollPosition().y + getStyleMeasureValue(getStyleValue(alleElemente[i], "margin-bottom"))); 330 } 331 } 332 var theBody = document.getElementsByTagName("BODY")[0]; 333 maxBottom = Math.max(maxBottom, theBody.getBoundingClientRect().bottom + getScrollPosition().y + getStyleMeasureValue(getStyleValue(theBody, "margin-bottom"))); 334 heightValue = maxBottom; 335 } 336 catch (except) 337 { 338 } 339 // 340 // Bei Fehlern wird die gut abgehangene HRGetDocumentHeight() als Fallback verwendet. 341 // 342 try 343 { 344 if (heightValue < 0) 345 { 346 heightValue = HRGetDocumentHeight(); 347 } 348 } 349 catch (except) 350 { 351 } 352 } 353 else 354 { 355 heightValue = getElementHeightValue(element) + getStyleMeasureValue(getStyleValue(element, "margin-top")) + getStyleMeasureValue(getStyleValue(element, "margin-bottom")); 356 } 357 return heightValue; 358} 359 360/** 361 * Liefert die Breite des Body-Tags. 362 * 363 * @returns Die Breite des Body-Tags in Pixeln. 364 */ 365function HRGetBodyWidth() 366{ 367 var theBody = document.getElementsByTagName("BODY")[0]; 368 var widthValue = getElementWidthValue(theBody); 369 return Math.ceil(widthValue); 370} 371 372/** 373 * Liefert die Höhe des Body-Tags. 374 * 375 * @returns Die Höhe des Body-Tags in Pixeln. 376 */ 377function HRGetBodyHeight() 378{ 379 var theBody = document.getElementsByTagName("BODY")[0]; 380 var heightValue = getElementHeightValue(theBody); 381 return Math.ceil(heightValue); 382} 383 384/** 385 * Ermittelt die Breite des ViewPorts. 386 * 387 * @see {https://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript} 388 * Stand: 12.12.2019 389 * 390 * @returns Die Breite des View-Ports. 391 */ 392function HRGetViewPortWidth() 393{ 394 return Math.max(document.documentElement.clientWidth, window.innerWidth || 0); 395} 396 397/** 398 * Ermittelt die Höhe des ViewPorts. 399 * 400 * @see {https://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript} 401 * Stand: 12.12.2019 402 * 403 * @returns Die Höhe des View-Ports. 404 */ 405function HRGetViewPortHeight() 406{ 407 return Math.max(document.documentElement.clientHeight, window.innerHeight || 0); 408} 409 410/** 411 * Ergänzt die übergebenen CSS-Klasse am Element. 412 * 413 * @param element Das Element, welches die Klasse erhalten soll. 414 * @param newCSSClass Die neue CSS-Klasse. 415 */ 416function addCSSClass(element, newCSSClass) 417{ 418 try 419 { 420 if (element && newCSSClass) 421 { 422 if (typeof element == "string") 423 element = document.getElementById(element); 424 425 if (element.className && element.className.length > 0) 426 { 427 var indexOfClass = element.className.indexOf(newCSSClass); 428 if ( indexOfClass < 0 429 || (indexOfClass + newCSSClass.length < element.className.length && element.className.charAt(indexOfClass + newCSSClass.length) != ' ') 430 || (indexOfClass > 0 && element.className.charAt(indexOfClass - 1) != ' ')) 431 { 432 element.className += ' ' + newCSSClass; 433 } 434 } 435 else 436 element.className = newCSSClass; 437 } 438 } 439 catch (e) 440 {} 441} 442 443/** 444 * Ergänzt die übergebenen CSS-Klasse an den HTML-Body. 445 * 446 * @param newCSSClass Die neue CSS-Klasse. 447 */ 448function HRAddBodyCSSClass(newCSSClass) 449{ 450 addCSSClass(document.getElementsByTagName('body')[0], newCSSClass); 451} 452 453/** 454 * @see HRCSS.removeCSSClass(element, cssClassToRemove) 455 * 456 * @deprecaded. 457 */ 458function removeCSSClass(element, cssClassToRemove) 459{ 460 HRCSS.removeCSSClass(element, cssClassToRemove); 461} 462 463/** 464 * @see HRCSS.containsCSSClass(element, cssClass) 465 * 466 * @deprecaded. 467 */ 468function HRContainsCSSClass(element, cssClass) 469{ 470 return HRCSS.containsCSSClass(element, cssClass); 471} 472 473/** 474 * Entfernt das erste Auftreten der übergebenen CSS-Klasse des HTML-Body. 475 * 476 * @param cssClassToRemove Die zu entfernende Klasse. 477 */ 478function HRRemoveBodyCSSClass(cssClassToRemove) 479{ 480 removeCSSClass(document.getElementsByTagName('body')[0], cssClassToRemove); 481} 482 483function replaceCSSClass(element, oldCSSClass, newCSSClass) 484{ 485 try 486 { 487 if (element && oldCSSClass && newCSSClass) 488 { 489 if (typeof element == "string") 490 element = document.getElementById(element); 491 492 if (element.className && element.className.length > 0) 493 { 494 var newClass = ""; 495 var containsElementOldClass = false;
496 var splitedClassNames = element.className.split(" "); 497 for (var index = 0; index < splitedClassNames.length; index++) 498 { 499 if (splitedClassNames[index] == oldCSSClass) 500 { 501 newClass = newClass + newCSSClass +" "; 502 containsElementOldClass = true; 503 } 504 else 505 { 506 newClass = newClass + splitedClassNames[index] +" "; 507 } 508 } 509 510 if(!containsElementOldClass) 511 { 512 newClass = newClass + newCSSClass; 513 } 514 element.className = newClass; 515 516 } 517 else 518 { 519 element.className = newCSSClass; 520 } 521 522 } 523 } 524 catch (e) 525 {} 526} 527 528/** 529 * Funktion ergänzt die übergebene CSS-Klasse bei dem Element, wenn sie noch nicht vorhanden ist, 530 * andernfalls wird sie entfernt. 531 * 532 * @param element das Element, bei dem die Klasse gesetzt/gelöscht werden soll. Bei Angabe einer Zeichenkette wird eine ID erwartet, ansonsten das Node-Objekt. 533 * @param cssClassToToggle Die CSS-Klasse die ergänzt oder entfernt werden soll. 534 */ 535function toggleCSSClass(element, cssClassToToggle) 536{ 537 try 538 { 539 if (element && cssClassToToggle && cssClassToToggle.length > 0) 540 { 541 if (typeof element == "string") 542 element = document.getElementById(element); 543 544 if (element.className && element.className.length > 0 && element.className.indexOf(cssClassToToggle) >= 0) 545 removeCSSClass(element, cssClassToToggle); 546 else 547 addCSSClass(element, cssClassToToggle); 548 } 549 } 550 catch (e) 551 {} 552} 553 554/** 555 * Bringt ein Element zur Anzeige, in dem die CSS-Eigenschaft display auf 556 * "block" bzw. "inline" (abhängig davon, ob es sich um ein Block-Element handelt) 557 * gesetzt wird. 558 * @param element Das Element, das dargestellt werden soll. Es kann die Id des Elements 559 * oder direkt das Element als Objekt übergeben werden. 560 */ 561function HRShow(element, displayType) 562{ 563 // TODO durch HRElem ersetzen. 564 try 565 { 566 if (element) 567 { 568 if (typeof element == "string") 569 element = document.getElementById(element); 570 571 if (element) 572 { 573 if (displayType != null) 574 { 575 element.style.display = displayType; 576 } 577 else 578 { 579 element.style.display = ''; 580 } 581 } 582 } 583 } 584 catch (e) 585 {} 586} 587 588/** 589 * Blendet ein Element aus, in dem die CSS-Eigenschaft display auf "none" gesetzt wird. 590 * 591 * @param element Das Element, das ausgeblendet werden soll. Es kann die Id des Elements 592 * oder direkt das Element als Objekt übergeben werden. 593 */ 594function HRHide(element) 595{ 596 // TODO durch HRElem ersetzen. 597 if (element) 598 { 599 try 600 { 601 if (typeof element == "string") 602 element = document.getElementById(element); 603 604 if (element) 605 { 606 element.style.display = 'none'; 607 } 608 } 609 catch (e) 610 { 611 if (typeof(HRLogError) === 'function') 612 HRLogError(e); 613 } 614 } 615} 616 617/** 618 * Getter für den (ggf. rekursiv bestimmten) ersten Parent-Knoten, der die gesuchte Style-Eigenschaft besitzt. 619 * 620 * @param element Das Ausgangselement. 621 * @param property Die gesuchte CSS-Eigenschaft. 622 * @param value Der gesuchte Wert zur CSS-Eigenschaft. 623 * @param recursive true, falls wir uns rekursiv den DOM-Baum hochhangeln sollen. 624 * @returns der Parent-Knoten, der das Attribut hat oder null, falls kein solcher Knoten gefunden wurde. 625 */ 626function getParentByStyleValue(element, property, value, recursive) 627{ 628 var result = null; 629 630 try 631 { 632 if (element && element.tagName !== 'HTML' && element.parentNode) 633 { 634 if (getStyleValue(element.parentNode, property) === value) 635 result = element.parentNode; 636 else if (recursive) 637 return getParentByStyleValue(element.parentNode, property, value, recursive); 638 } 639 } 640 catch (e) 641 { 642 if (typeof(HRLogError) === 'function') 643 HRLogError(e); 644 } 645 646 return result; 647} 648 649/** 650 * Bringt die Elemente im übergebenen Array auf die selbe Höhe (diese entspricht der größten Höhe aus allen Elementen). 651 * 652 * @param array Das Array mit allen Elementen, die auf die selbe Höhe gebracht werden sollen. 653 */ 654function equalizeHeight(array) 655{ 656 try 657 {
658 if (array) 659 { 660 array = Array.from(array); 661 662 // Ermittelt die Maximalhöhe aller im Array befindlicher Elemente. 663 var maxHeight = 0; 664 Array.from(array).forEach(el => { 665 maxHeight = Math.max(getElementHeightValue(el), maxHeight); 666 }); 667 668 // Alle Elemente auf die Maximalhöhe bringen. 669 Array.from(array).forEach(el => { 670 el.style.height = maxHeight + "px"; 671 }); 672 } 673 } 674 catch (e) 675 { 676 if (typeof(HRLogError) === 'function') 677 HRLogError(e); 678 } 679} 680 681/** 682 * Führt eine Zählanimation (von z.B. 0 bis 100 zählen) im Viewport aus. 683 * 684 * @param span Ist das Element, welches animiert wird. 685 * @param start Ist die erste Zahl, der Animation. 686 * @param end Letzte Zahl. Danach bricht die Funktion ab. 687 * @param durationSec Die Dauer der Animation in Sekunden. 688 */ 689function doCountAnimation(el, start, end, durationSec) 690{ 691 try 692 { 693 // Animation darf nur einmal ausgeführt werden. 694 let already = false; 695 let duration = 10000; 696 if(durationSec){ 697 duration = durationSec * 1000; 698 } 699 if (isInViewport(el)) 700 { 701 calcCountAnimation(el, start, end, duration); 702 } 703 else 704 { 705 // Erst durch das Scrollen, wird der Zähler sichtbar. 706 window.addEventListener('scroll', function () { 707 if (isInViewport(el)) 708 { 709 if (!already) 710 { 711 calcCountAnimation(el, start, end, duration); 712 already = true; 713 } 714 } 715 }); 716 } 717 } 718 catch (e) 719 { 720 if (typeof(HRLogError) === 'function') 721 HRLogError(e); 722 } 723} 724 725/** 726 * Führt die Animation (von z.B. 0 bis 100 zählen) aus. 727 * 728 * @param el Ist das Element, welches animiert wird. 729 * @param start Ist die erste Zahl, der Animation. 730 * @param end Letzte Zahl. Danach bricht die Funktion ab. 731 * @param onStep Callback-Funktion, die in jedem Schritt der Animation ausgeführt wird. 732 */ 733function calcCountAnimation(el, start, end, duration, onStep) 734{ 735 try 736 { 737 let startTimestamp = null; 738 const step = (timestamp) => { 739 740 if (!startTimestamp) 741 { 742 startTimestamp = timestamp; 743 } 744 // Misst die vergangene Zeit. 745 const progress = Math.min((timestamp - startTimestamp) / duration, 1); 746 747 // Span erhält neuen Wert. 748 el.innerHTML = Math.floor(progress * (end - start) + start).toString().replace(/\B(?=(\d{3})+(?!\d))/g, "."); 749 750 if (onStep && typeof(onStep) === 'function') 751 onStep(Math.floor(progress * (end - start) + start)); 752 753 // Schritte werden wieder wiederholt. 754 if (progress < 1) { 755 window.requestAnimationFrame(step); 756 } 757 }; 758 window.requestAnimationFrame(step); 759 } 760 catch (e) 761 { 762 if (typeof(HRLogError) === 'function') 763 HRLogError(e); 764 } 765} 766 767/** 768 * @deprecated: Ersetzt durch statischen Aufruf HRCSS.isInViewport. 769 */ 770function isInViewport(el) 771{ 772 return HRCSS.isInViewport(el); 773} 774 775"use strict"; 776 777class HRCSS 778{ 779 /** 780 * Stack für die geöffneten Overlays. 781 */ 782 static openOverlays = []; 783 784 /** 785 * Entfernt das erste Auftreten der übergebenen CSS-Klasse des Elementes. 786 * 787 * @param element Das Element, dessen Klasse entfernt werden soll, bzw. 788 * eine Zeichenkette mit dessen Id. 789 * @param cssClassToRemove Die zu entfernende Klasse. 790 */ 791 static removeCSSClass(element, cssClassToRemove) 792 { 793 try 794 { 795 if (!element || !cssClassToRemove) 796 return; 797 798 if (typeof element === 'string') 799 element = document.getElementById(element); 800 801 if (element) 802 { 803 if (element.classList.contains(cssClassToRemove)) 804 element.classList.remove(cssClassToRemove); 805 806 // Wenn keine Klassen mehr übrig sind, das class-Attribut entfernen 807 if (element.classList.length === 0) 808 element.removeAttribute('class'); 809 } 810 } 811 catch (e) 812 { 813 // darf ruhig bleiben... 814 } 815 } 816 817 /** 818 * Prüft, ob das angegebene Element die CSS-Klasse trägt. 819 * 820 * @param element Das Element, das geprüft werden soll. 821 * @param cssClass die gesuchte CSS-Klasse. 822 */ 823 static containsCSSClass(element, cssClass) 824 {
825 try 826 { 827 if (element && cssClass && cssClass.length > 0) 828 { 829 if (typeof element == "string") 830 element = document.getElementById(element); 831 832 if (element) 833 return element.classList.contains(cssClass); 834 } 835 } 836 catch (e) 837 { 838 } 839 840 return false; 841 } 842 843 /** 844 * Getter für die CSS-Klassen eines DOM-Elements. 845 * 846 * @param node Das DOM-Element, dessen Klassen ermittelt werden sollen. 847 * @return die CSS-Klassen als Array. 848 */ 849 static getClasses(node) 850 { 851 if (node && node.className) 852 return node.className.trim().split(' ').filter(className => !className.trim().isEmpty()).map(classname => classname.trim()); 853 854 return []; 855 } 856 857 /** 858 * Prüft, ob das Element vollständig im Viewport ist. Für den Fall, dass man prüfen will, 859 * ob das Element nur teilweise im Viewport liegt, @see CSS.intersectViewport. 860 * 861 * @param el Element, welches überprüft werden soll. 862 * @return Gibt true zurück, wenn das Element vollständig im Viewport liegt. 863 */ 864 static isInViewport(el) 865 { 866 const rect = el.getBoundingClientRect(); 867 return (rect.top >= 0 868 && rect.left >= 0 869 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) 870 && rect.right <= (window.innerWidth || document.documentElement.clientWidth)); 871 } 872 873 /** 874 * Prüft, ob ein Teil des übergebenen Elementes im Viewport liegt. Für den Fall, dass man prüfen will, 875 * ob das Element vollständig im Viewport liegt, @see CSS.isInViewport. 876 * 877 * @param el Element, welches überprüft werden soll. 878 * @return Gibt true zurück, wenn das Element teilweise den Viewport schneidet. Nur, wenn das Element vollständig 879 * außerhalb des Vieworts liegt, wird false ausgegeben. 880 */ 881 static intersectsViewport(el) 882 { 883 const rect = el.getBoundingClientRect(); 884 return (rect.top >= -el.offsetHeight 885 && rect.left >= -el.offsetWidth 886 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + el.offsetHeight 887 && rect.right <= (window.innerWidth || document.documentElement.clientWidth) + el.offsetWidth); 888 } 889 890 /** 891 * Prüft, ob ein gegebenes DOM-Element sichtbar ist (display.none oder hidden). Neben dem Element selbst 892 * werden auch Elternelemente geprüft. 893 * 894 * @param el Das zu prüfende Element. 895 * @return true, falls das DOM-Element nicht sichtbar ist. 896 */ 897 static isHidden(el) 898 { 899 try 900 { 901 // Durchläuft die gesamte Elternelement-Kette bis zur Wurzel. 902 for (let current = el; current; current = current.parentElement) 903 { 904 const style = window.getComputedStyle(current); 905 906 // Prüfen, ob das Element (oder eines seiner Elternelemente) ausgeblendet ist. 907 if (style.display === 'none' || style.visibility === 'hidden') 908 return true; 909 } 910 return false; 911 } 912 catch (e) 913 { 914 if (typeof(HRLogError) === 'function') 915 HRLogError(e); 916 } 917 918 return true; 919 } 920 921 /** 922 * Ermittelt die Einheit von CSS-Ausdrücken wie 100px, 5rem, 10% etc. 923 * 924 * @param expression Der CSS-Ausdruck. 925 * @return Die Einheit des Ausdrucks oder null, falls keine Einheit gefunden werden konnte. 926 */ 927 static getUnit(expression) 928 { 929 try 930 { 931 const unitMatch = expression.match(/[a-zA-Z%]+/); 932 return unitMatch ? unitMatch[0].trim() : null; 933 } 934 catch (e) 935 { 936 if (typeof(HRLogError) === 'function') 937 HRLogError(e); 938 } 939 } 940 941 /** 942 * Toggelt die Sichtbarkeit eines übergebenen Elements. Es wird mit der 'hide'-Klasse gearbeitet, 943 * die in unserem Framework immer als Klasse vorhanden sein sollte. 944 * 945 * @param element Das Element, dessen Sichtbarkeit getoggelt werden soll. 946 * @param isVisible true, falls das Element sichtbar sein soll und false, wenn es versteckt werden soll. 947 */ 948 static toggleVisibility(element, isVisible) 949 { 950 if (element) 951 element.classList.toggle('hide', !isVisible); 952 } 953 954 /** 955 * Steuert den Fokus für ein Overlay mit Toggle-Button. 956 * 957 * @param toggleButton Der Button, der das Overlay öffnen und schließen soll. 958 * @param overlayElement Das Overlay, was getoggelt wird. 959 * @param optionalCloseButton: Ein optionaler Schließen-Button. Ist dieser gesetzt, wird der Toggle für's Schließen nicht mehr fokussiert. 960 * @param optionalDisableBodyScroll: Sorgt dafür, dass der Body die Klasse "no-scroll" toggelt. 961 * @param allowMultipleOverlays: Sorgt dafür, dass auch mehrere Overlays geöffnet werden können. 962 * @param allowFocusEscape: Erlaubt den Fokus außerhalb des Overlays. 963 */ 964 static toggleOverlay(toggleButton, overlayElement, optionalCloseButton, optionalDisableBodyScroll, allowMultipleOverlays, allowFocusEscape) 965 { 966 /** 967 * Liefert alle fokussierbaren Elemente innerhalb eines Elements. 968 * 969 * @param element Das Wurzelelement für die Suche. 970 * @returns Array von fokussierbaren Elementen. 971 */ 972 const getFocusableElements = (element = document) => 973 [...element.querySelectorAll( 974 'a[href], button:not([disabled]), input:not([type="hidden"]):not([disabled]), textarea, select, details, [tabindex]:not
974([tabindex="-1"])' 975 )].filter(el => 976 !el.hasAttribute('aria-hidden') && !HRCSS.isHidden(el) 977 ); 978 979 /** 980 * Fokus-Falle für Toggle-Button (und optional Schließen-Button): Verhindert, dass mit der Tabulatortaste der Fokus außerhalb des 981 * Overlays wandert, wenn der Button selbst fokussiert ist. Stattdessen wird direkt zum ersten oder letzten 982 * fokussierbaren Element im Overlay gesprungen. 983 * 984 * @param e Das übergebene Tastatur-Event (keydown). 985 */ 986 const handleButtonFocusTrap = function(e) { 987 const focusableElements = getFocusableElements(overlayElement); 988 const first = focusableElements[0]; 989 const last = focusableElements[focusableElements.length - 1]; 990 991 if (e.key === 'Tab') 992 { 993 e.preventDefault(); 994 e.shiftKey ? last.focus() : first.focus(); 995 } 996 }; 997 998 /** 999 * Fokus-Falle für Elemente innerhalb des Overlays: Verhindert, dass der Fokus beim Erreichen des ersten oder letzten Elements 1000 * mit Tab bzw. Shift+Tab nach draußen wandert. Stattdessen wird der Fokus 1001 * zum definierten Startpunkt zurückgesetzt. 1002 * 1003 * @param e Das KeyboardEvent bei Tastendruck. 1004 */ 1005 const handleFocusTrap = function(e) { 1006 if (e.key !== 'Tab') 1007 return; 1008 1009 const focusableElements = getFocusableElements(overlayElement); 1010 const first = focusableElements[0]; 1011 const last = focusableElements[focusableElements.length - 1]; 1012 const focusTarget = optionalCloseButton || toggleButton; 1013 1014 if ((e.shiftKey && document.activeElement === first) || (!e.shiftKey && document.activeElement === last)) 1015 { 1016 if (focusTarget === document.activeElement) 1017 { 1018 if (!allowFocusEscape) 1019 { 1020 e.preventDefault(); 1021 e.shiftKey ? last.focus(): first.focus(); 1022 } 1023 else if (HRCSS.openOverlays.length > 0) 1024 closeOverlay(HRCSS.openOverlays.at(-1).overlayElement); 1025 } 1026 else if (!allowFocusEscape) 1027 { 1028 if (!HRCSS.isHidden(focusTarget)) 1029 { 1030 e.preventDefault(); 1031 focusTarget.focus(); 1032 } 1033 // Sonderfall: Der optionale Schließen-Button ist zurzeit nicht verfügbar. Wechsel auf den Toggle-Button. 1034 else if (!HRCSS.isHidden(toggleButton)) 1035 { 1036 e.preventDefault(); 1037 toggleButton.focus(); 1038 } 1039 } 1040 } 1041 }; 1042 1043 /** 1044 * Öffnet oder schließt das Overlay. 1045 * 1046 * Beim Öffnen: 1047 * - Fügt nötige Klassen/Attribute hinzu 1048 * - Setzt Fokus ggf. auf Schließen-Button 1049 * - Aktiviert Fokus-Fallen (Focus Trap) 1050 * - Optional: Scrollverhalten deaktivieren (no-scroll) 1051 * - Speichert Overlay-Zustand in Stack (openOverlays) 1052 * 1053 * Beim Schließen: 1054 * - Entfernt alle Listener & Klassen 1055 * - Reaktiviert Scrollverhalten 1056 * - Entfernt Overlay aus Stack 1057 */ 1058 const toggle = () => { 1059 1060 const isOpen = overlayElement.classList.toggle('overlay-open'); 1061 1062 if (isOpen) // Öffnet Overlay: 1063 { 1064 if (!allowMultipleOverlays) 1065 { 1066 // Alle offenen Overlays schließen. 1067 while (HRCSS.openOverlays.length > 0) 1068 { 1069 const lastOverlay = HRCSS.openOverlays.at(-1); 1070 if (lastOverlay?.toggleButton && lastOverlay.overlayElement && lastOverlay.overlayElement.classList.contains('overlay-open')) 1071 lastOverlay.toggleButton.click(); 1072 else 1073 { 1074 if (lastOverlay?.handlers) 1075 closeOverlay(lastOverlay.overlayElement); 1076 else 1077 HRCSS.openOverlays.pop(); 1078 } 1079 lastOverlay?.toggleButton.blur(); 1080 } 1081 } 1082 1083 if (optionalCloseButton) 1084 setTimeout(() => optionalCloseButton.focus(), 200); 1085 1086 setTimeout(() => { 1087 const focusableElements = getFocusableElements(overlayElement);
1088 focusableElements.forEach(el => { 1089 el.addEventListener('keydown', handleFocusTrap); 1090 }); 1091 }, 200); 1092 1093 toggleButton.addEventListener('keydown', handleButtonFocusTrap); 1094 1095 if (optionalCloseButton) 1096 { 1097 optionalCloseButton.addEventListener('keydown', handleFocusTrap); 1098 optionalCloseButton.addEventListener('click', () => { 1099 closeOverlay(overlayElement); 1100 }); 1101 } 1102 1103 if (!optionalDisableBodyScroll) 1104 document.body.classList.add('no-scroll'); 1105 1106 toggleButton.classList.add('open'); 1107 toggleButton.setAttribute('aria-expanded', 'true'); 1108 addCSSClass(overlayElement, 'overlay-open') 1109 1110 HRCSS.openOverlays.push({ 1111 overlayElement, 1112 toggleButton, 1113 optionalCloseButton, 1114 optionalDisableBodyScroll, 1115 allowFocusEscape, 1116 handlers: { 1117 focusTrap: handleFocusTrap, 1118 buttonTrap: handleButtonFocusTrap 1119 } 1120 }); 1121 } 1122 else // Schließt Overlay: 1123 { 1124 closeOverlay(overlayElement); 1125 } 1126 }; 1127 1128 /** 1129 * Das Overlay wird wieder geschlossen. 1130 * 1131 * @param overlayElement Overlay, welches geschlossen wird. 1132 */ 1133 const closeOverlay = (overlayElement) => { 1134 const overlay = HRCSS.openOverlays.find(o => o.overlayElement === overlayElement); 1135 if (!overlay) 1136 return; 1137 1138 const { toggleButton, optionalCloseButton, optionalDisableBodyScroll, handlers } = overlay; 1139 const { focusTrap, buttonTrap } = handlers || {}; 1140 // Liefert alle potenziell fokussierbaren Elemente (auch die zurzeit nicht sichtbar sind). 1141 const focusableElements = overlayElement.querySelectorAll( 1142 'a[href], button:not([disabled]), input:not([type="hidden"]):not([disabled]), textarea, select, details, [tabindex]:not([tabindex="-1"])' 1143 ); 1144 1145 if (typeof focusTrap === 'function') 1146 focusableElements.forEach(el => el.removeEventListener('keydown', focusTrap)); 1147 1148 if (typeof buttonTrap === 'function') 1149 toggleButton.removeEventListener('keydown', buttonTrap); 1150 1151 if (optionalCloseButton) 1152 optionalCloseButton.removeEventListener('keydown', focusTrap); 1153 1154 overlayElement.classList.remove('overlay-open'); 1155 toggleButton.classList.remove('open'); 1156 toggleButton.setAttribute('aria-expanded', 'false'); 1157 toggleButton.focus(); 1158 1159 if (!optionalDisableBodyScroll) 1160 document.body.classList.remove('no-scroll'); 1161 1162 HRCSS.openOverlays = HRCSS.openOverlays.filter(o => o.overlayElement !== overlayElement); 1163 }; 1164 1165 toggleButton?.addEventListener('click', toggle); 1166 1167 // Code für das Schließen des zuletzt geöffneten Menü auf Escape. 1168 if (!HRCSS._escapeHandlerRegistered) { 1169 window.addEventListener('keydown', (e) => { 1170 if (e.key === 'Escape' && HRCSS.openOverlays.length > 0) 1171 { 1172 let lastOverlay = HRCSS.openOverlays[HRCSS.openOverlays.length - 1]; 1173 // Für den Fall, dass aus dem Focus ausgebrochen werden kann, wird der gesamte Teil des Overlays geschlossen. 1174 if (lastOverlay?.allowFocusEscape) 1175 { 1176 while (lastOverlay?.allowFocusEscape) 1177 { 1178 if (lastOverlay.optionalCloseButton) 1179 lastOverlay.optionalCloseButton.click(); 1180 else if (lastOverlay.toggleButton) 1181 lastOverlay.toggleButton.click(); 1182 lastOverlay = HRCSS.openOverlays[HRCSS.openOverlays.length - 1]; 1183 } 1184 } 1185 else // Hier wird das letzte Overlay geschlossen. 1186 { 1187 if (lastOverlay?.optionalCloseButton) 1188 lastOverlay.optionalCloseButton.click(); 1189 else if (lastOverlay?.toggleButton) 1190 lastOverlay.toggleButton.click(); 1191 } 1192 } 1193 }); 1194 HRCSS._escapeHandlerRegistered = true; 1195 } 1196 } 1197}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.