https://www.dfwconnectedcare.com/-/media/project/bsw/sites/bswhealth/scripts/bswh-anchor-nav.js?v=31
1window.addEventListener("DOMContentLoaded", () => { 2 jQuery(function ($) { 3 const anchornavClass = function (config) { 4 // Merge defaults and config 5 this.settings = $.extend({}, this.defaults, config); 6 }; 7 8 anchornavClass.prototype.defaults = { 9 anchorNav: ".anchor-nav", 10 anchorLink: ".anchor-link", 11 backTopLink: ".back-top-link", 12 }; 13 14 anchornavClass.prototype.settings = { 15 skipping: null, 16 anchorNavBottom: 0, 17 bannerHeight: 0, 18 }; 19 20 anchornavClass.prototype.init = function () { 21 this.setupNavLinks(); 22 this.handleStickyAnchorNav(); 23 this.backToTop(); 24 this.checkScroll(); 25 }; 26 27 anchornavClass.prototype.checkScroll = function () { 28 const hash = window.location.hash; 29 30 if (hash) { 31 setTimeout(() => { 32 this.scrollPageToEl(hash.substring(1), 150); 33 }); 34 } 35 }; 36 37 anchornavClass.prototype.toSlug = function (value) { 38 const lowercased = value.toLowerCase(); 39 const slug = lowercased 40 .replace(/\./g, "") 41 .replace(/[^\w\s-]/g, " ") 42 .replace(/\s+/g, "-"); 43 return slug; 44 }; 45 46 anchornavClass.prototype.hasBSWHBanner = function () { 47 const bswhealthBanner = $(".bswh-page-banner"); 48 49 return bswhealthBanner.length > 0; 50 }; 51 52 anchornavClass.prototype.scrollPageToEl = function ( 53 elemId, 54 overridetimeToTake = null 55 ) { 56 ScrollTrigger.refresh(); 57 58 if (!elemId) { 59 return; 60 } 61 62 const elToScrollTo = $(`[data-href="${elemId}"]`)[0]; 63 64 if (!elToScrollTo) { 65 return; 66 } 67 68 let timeToTake = isNaN(overridetimeToTake) 69 ? 1000 70 : overridetimeToTake; 71 let extraOffset = 0; 72 const anchorNav = $(".anchor-nav"); 73 const { height: anchorHeight, bottom: anchorBottom } = 74 anchorNav[0].getBoundingClientRect(); 75 const windowScroll = $(window).scrollTop(); 76 const sectionPadding = elToScrollTo.closest(".section-padding"); 77 const header = $("header"); 78 const { height: headerHeight } = header[0].getBoundingClientRect(); 79 let offset = $(elToScrollTo).offset().top; 80 81 if (this.hasBSWHBanner()) { 82 extraOffset = anchorBottom; 83 } else { 84 if (windowScroll <= 0) { 85 // Not sure why it needs double the height 86 // of the anchor navigation, but this works. 87 extraOffset = headerHeight + anchorHeight; 88 } else { 89 extraOffset = anchorBottom; 90 } 91 } 92 93 if (sectionPadding) { 94 offset = $(sectionPadding).offset().top; 95 } 96 97 // The 1 is just to make sure we get over the line to detect we're 98 // in the section. 99 const scrollTo = offset - extraOffset + 1; 100 101 // Want to check how much we are scrolling and always use a 102 // positive number to ease calculations. 103 const scrollDiff = Math.abs(windowScroll - scrollTo); 104 105 // Don't want to take the full time if the selected element is close. 106 if (scrollDiff <= 800 && isNaN(overridetimeToTake)) { 107 timeToTake = 500; 108 } 109 110 $("html, body").animate({ scrollTop: scrollTo }, timeToTake); 111 }; 112 113 anchornavClass.prototype.createNavLinkList = function (sectionList) { 114 const linkList = []; 115 116 for (const sectionElem of sectionList) { 117 const $elem = $(sectionElem); 118 let datahref = $elem.attr("data-href"); 119 let datahrefLabel = $elem.attr("data-hreflabel"); 120 121 if (datahref == "" || datahref == "undefined") { 122 return; 123 } else { 124 if (!datahrefLabel) { 125 datahrefLabel = datahref; 126 datahref = this.toSlug(datahref); 127 } 128 const $divContainer = $("<div>", { 129 class: "anchor-link-wrapper", 130 }); 131 const $anchor = $("<a>", { 132 href: `#${datahref}`, 133 class: "anchor-link", 134 text: datahrefLabel, 135 "data-scrollsection": datahref, 136 "data-analytics-element": "anchor_nav", 137 }); 138 $divContainer.append($anchor); 139 $divContainer.append( 140 $("<div>", { 141 class: "anchor-link--marker", 142 }) 143 ); 144 145 linkList.push($divContainer); 146 } 147 } 148 149 return linkList; 150 }; 151 152 anchornavClass.prototype.setupNavLinks = function () { 153 const thatAnchorNav = this; 154 const $navLinkContainer = $(".anchor-nav .nav-link-wrapper"); 155 $navLinkContainer.empty(); 156 const $fragment = $(new DocumentFragment()); 157 const sectionList = $("div[data-href]"); 158 const linkList = this.createNavLinkList(sectionList); 159 160 for (const section of sectionList) { 161 const datahref = $(section).attr("data-href"); 162 163 for (const link of linkList) { 164 if ( 165 $($(link).children()[0]).attr("href") === `#${datahref}` 166 ) { 167 $(link).click((e) => { 168 e.preventDefault(); 169 170 for (const linkInternal of linkList) { 171 $(linkInternal).removeClass("active"); 172 } 173 174 $(link).addClass("active"); 175 thatAnchorNav.settings.skipping = datahref; 176 history.pushState(null, null, `#${datahref}`); 177 thatAnchorNav.scrollPageToEl(datahref); 178 }); 179 180 $fragment.append(link); 181 } 182 } 183 } 184 185 $navLinkContainer.append($fragment); 186 }; 187 188 anchornavClass.prototype.handleStickyAnchorNav = function () { 189 const thatClass = this; 190 const navLinkWrapper = document.querySelector(".nav-link-wrapper"); 191 192 if ( 193 typeof gsap !== "undefined" && 194 typeof ScrollTrigger !== "undefined" 195 ) { 196 const sections = gsap.utils.toArray("div[data-href]"); 197 const anchorLinkWrappers = gsap.utils.toArray( 198 ".anchor-link-wrapper" 199 ); 200 const anchorNav = document.querySelector(".anchor-nav"); 201 const toTop = document.querySelector(".back-top-link-wrapper"); 202 203 gsap.to(toTop, { 204 ease: "none", 205 duration: 0.25, 206 transformOrigin: "right", 207 scrollTrigger: { 208 trigger: "body", 209 start: "top top", 210 end: "bottom center", 211 toggleActions: "play none none reverse", 212 onEnter: () => { 213 gsap.to(toTop, { 214 display: "flex", 215 opacity: 1, 216 }); 217 }, 218 onLeaveBack: () => { 219 gsap.to(toTop, { 220 display: "none", 221 opacity: 0, 222 }); 223 thatClass.settings.skipping = null; 224 }, 225 }, 226 }); 227 let scrollTriggerTimeout = null; 228 const sectionObserver = new ResizeObserver(() => { 229 if (scrollTriggerTimeout) { 230 clearTimeout(scrollTriggerTimeout); 231 } 232 233 scrollTriggerTimeout = setTimeout(() => { 234 ScrollTrigger.refresh(); 235 }, 100); 236 }); 237
238 [...sections].forEach((section, i) => { 239 const marker = anchorLinkWrappers[i]?.querySelector( 240 ".anchor-link--marker" 241 ); 242 243 if (!marker) return; 244 245 let href = ""; 246 const anchorLink = 247 anchorLinkWrappers[i]?.querySelector(".anchor-link"); 248 249 const componentWithHref = section.querySelector( 250 ".component[data-href]" 251 ); 252 253 if (componentWithHref) { 254 href = componentWithHref.dataset.href; 255 } else { 256 href = section.dataset.href; 257 } 258 259 let finalSection = section; 260 const hasSectionPadding = 261 section.closest(".section-padding"); 262 263 if (hasSectionPadding) { 264 finalSection = hasSectionPadding; 265 } 266 267 const enterMarker = () => { 268 const { skipping } = thatClass.settings; 269 270 if (!skipping || (skipping && skipping === href)) { 271 gsap.to(marker, { 272 transformOrigin: "left", 273 scaleX: 1, 274 }); 275 thatClass.settings.skipping = null; 276 const { 277 left: anchorLinkLeft, 278 width: anchorLinkWidth, 279 } = anchorLink.getBoundingClientRect(); 280 const { width: navLinkWrapperWidth } = 281 navLinkWrapper.getBoundingClientRect(); 282 const menuScrollLeft = navLinkWrapper.scrollLeft; 283 const anchorLinkPosition = 284 anchorLinkLeft + 285 anchorLinkWidth / 2 + 286 menuScrollLeft - 287 navLinkWrapperWidth / 2; 288 navLinkWrapper.scrollLeft = anchorLinkPosition; 289 anchorLink.classList.add("active"); 290 } 291 }; 292 const leaveMarker = () => { 293 gsap.to(marker, { 294 transformOrigin: "right", 295 scaleX: 0, 296 }); 297 anchorLink.classList.remove("active"); 298 }; 299 let anchorNavBottom = 300 anchorNav.getBoundingClientRect().bottom; 301 302 gsap.to(marker, { 303 ease: "none", 304 duration: 0.25, 305 overwrite: "auto", 306 scrollTrigger: { 307 trigger: finalSection, 308 start: () => { 309 return `top ${anchorNavBottom}px`; 310 }, 311 end: () => { 312 return `bottom ${anchorNavBottom}px`; 313 }, 314 toggleActions: "play none none reverse", 315 scrub: false, 316 markers: false, 317 fastScrollEnd: true, 318 preventOverlaps: true, 319 onEnter: enterMarker, 320 onEnterBack: enterMarker, 321 onLeave: leaveMarker, 322 onLeaveBack: leaveMarker, 323 onUpdate: (self) => { 324 // Because of the nature of things changing during scroll 325 // we need to do our best to update data during the lifecycle 326 // so we can better show what the current state is. 327 const newAnchorNavBottom = 328 anchorNav.getBoundingClientRect().bottom; 329 330 // Only update when needed. 331 if (newAnchorNavBottom !== anchorNavBottom) { 332 anchorNavBottom = newAnchorNavBottom; 333 self.start = newAnchorNavBottom; 334 self.end = newAnchorNavBottom; 335 self.refresh(); 336 } 337 }, 338 }, 339 }); 340 341 sectionObserver.observe(section); 342 }); 343 } 344 }; 345 346 /** 347 * Want to scroll to the top of the page and use a ratio to determine 348 * how long it should take to scroll to the top. 349 */ 350 anchornavClass.prototype.backToTop = function () { 351 const thatClass = this; 352 353 $(this.settings.backTopLink).on("click", function (e) { 354 e.stopPropagation(); 355 356 thatClass.settings.skipping = "top"; 357 const pageHeight = $("body").height(); 358 const scrollLoc = $(window).scrollTop(); 359 const percentage = Math.floor((scrollLoc / pageHeight) * 100); 360 const timeToTake = (1000 * percentage) / 100; 361 362 $("html, body").animate({ scrollTop: 0 }, timeToTake); 363 }); 364 }; 365 366 anchornavClass.prototype.setAnchorNavTop = function () { 367 const header = $("#global-multi-header"); 368 const { height: headerHeight } = header[0].getBoundingClientRect(); 369 370 $(".anchor-nav").css({ top: `${headerHeight}px` }); 371 }; 372 373 anchornavClass.prototype.mobileFunctions = function () { 374 const buttonGroup = document.querySelector( 375 ".anchor-nav .cta-wrapper" 376 ); 377 378 if (buttonGroup) { 379 const body = document.body; 380 381 ScrollTrigger.create({ 382 id: "ctaMobileAnchorNav", 383 markers: false, 384 trigger: body, 385 start: "top -20%", 386 onUpdate: (self) => { 387 if (self.direction === 1) { 388 body.classList.add("scrolling-down"); 389 body.classList.remove("scrolling-up"); 390 gsap.to(buttonGroup, { yPercent: 100 }); 391 } else { 392 body.classList.add("scrolling-up"); 393 body.classList.remove("scrolling-down"); 394 gsap.to(buttonGroup, { yPercent: 0 }); 395 } 396 }, 397 onLeaveBack: () => { 398 body.classList.remove("scrolling-up"); 399 }, 400 }); 401 } 402 403 return () => { 404 const ctaMobileAnchorNav = 405 ScrollTrigger.getById("ctaMobileAnchorNav"); 406 407 if (ctaMobileAnchorNav) { 408 ctaMobileAnchorNav.kill(true); 409 } 410 }; 411 }; 412 413 $(function () { 414 let timer = null; 415 let anchornavtInit = new anchornavClass(); 416 417 window.scrollPageToEl = 418 anchornavtInit.scrollPageToEl.bind(anchornavtInit); 419 420 if ( 421 typeof gsap !== "undefined" && 422 typeof ScrollTrigger !== "undefined" 423 ) { 424 gsap.registerPlugin(ScrollTrigger); 425 426 const mmBanner = gsap.matchMedia(); 427 mmBanner.add( 428 `(max-width: 991px)`, 429 anchornavtInit.mobileFunctions 430 ); 431 } else { 432 console.error("Error initializing GSAP in Anchor Navigation"); 433 } 434
435 $(".anchor-nav").length && anchornavtInit.init(); 436 437 if (!anchornavtInit.hasBSWHBanner()) { 438 anchornavtInit.setAnchorNavTop(); 439 440 $(".alert-closebtn").click(() => { 441 let visibleTimer = null; 442 443 try { 444 const header = $("header"); 445 const { bottom: startHeaderBottom } = 446 header[0].getBoundingClientRect(); 447 448 visibleTimer = setInterval(() => { 449 const { bottom: currentHeaderBottom } = 450 header[0].getBoundingClientRect(); 451 452 if (currentHeaderBottom !== startHeaderBottom) { 453 anchornavtInit.setAnchorNavTop(); 454 ScrollTrigger.refresh(); 455 clearInterval(visibleTimer); 456 } 457 }, 100); 458 } catch (e) { 459 console.error(e); 460 } 461 }); 462 } 463 464 $(window).on("resize", () => { 465 if (timer) { 466 clearTimeout(timer); 467 } 468 469 timer = setTimeout(() => { 470 if (!anchornavtInit.hasBSWHBanner()) { 471 anchornavtInit.setAnchorNavTop(); 472 } 473 }, 300); 474 }); 475 }); 476 }); 477});
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.