Skip to content

Commit 5a756c8

Browse files
committed
feat: enhance PromoBanner to eliminate repaints
1 parent 6a3d347 commit 5a756c8

File tree

2 files changed

+29
-6
lines changed

2 files changed

+29
-6
lines changed

src/components/banner/PromoBanner.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ const BASE_MESSAGE_CLASSNAME = "text-lg font-semibold";
1717
const BASE_BUTTON_CLASSNAME =
1818
"flex h-8 justify-center items-center px-4 rounded-md font-semibold";
1919

20+
type PromoBannerProps = {
21+
requestPath?: string;
22+
seed?: string;
23+
};
24+
2025
const STATIC_PROMOS: PromoData[] = Object.values(promoData);
2126

2227
const isPromoActive = (promo: PromoData | null | undefined) =>
@@ -57,7 +62,18 @@ const getEligiblePromos = (promos: PromoData[], os: string | null) =>
5762
return promo.osTargets.includes(os);
5863
});
5964

60-
const selectWeightedPromo = (promos: PromoData[]) => {
65+
const normalizeSeed = (seed: string | null | undefined) =>
66+
seed && seed.length > 0 ? seed : "default";
67+
68+
const deterministicRandom = (seed: string) => {
69+
let hash = 0;
70+
for (let index = 0; index < seed.length; index += 1) {
71+
hash = (hash * 31 + seed.charCodeAt(index)) | 0;
72+
}
73+
return (hash >>> 0) / 0xffffffff;
74+
};
75+
76+
const selectWeightedPromo = (promos: PromoData[], seed: string) => {
6177
if (promos.length === 0) {
6278
return null;
6379
}
@@ -69,7 +85,7 @@ const selectWeightedPromo = (promos: PromoData[]) => {
6985
return getHighestPriorityPromo(promos);
7086
}
7187

72-
let threshold = Math.random() * totalWeight;
88+
let threshold = deterministicRandom(seed) * totalWeight;
7389

7490
for (let index = 0; index < promos.length; index += 1) {
7591
threshold -= weights[index];
@@ -85,15 +101,18 @@ const selectWeightedPromo = (promos: PromoData[]) => {
85101
const buildPromoList = (path: string | null): PromoData[] =>
86102
STATIC_PROMOS.filter((promo) => !isSuppressedOnPath(promo, path));
87103

88-
const PromoBanner: React.FC = () => {
104+
const PromoBanner: React.FC<PromoBannerProps> = ({ requestPath, seed }) => {
89105
const browserOS = useBrowserOS();
90-
const pathName = typeof window !== "undefined" ? window.location.pathname : null;
106+
const pathName =
107+
requestPath ??
108+
(typeof window !== "undefined" ? window.location.pathname : null);
91109
const promos = buildPromoList(pathName);
92110
const eligiblePromos = getEligiblePromos(promos, browserOS);
93111
const fallbackPromos = promos.filter((promo) => isPromoActive(promo));
94112
const selectionPool =
95113
eligiblePromos.length > 0 ? eligiblePromos : fallbackPromos;
96-
const selectedPromo = selectWeightedPromo(selectionPool);
114+
const selectionSeed = normalizeSeed(seed ?? pathName ?? "default");
115+
const selectedPromo = selectWeightedPromo(selectionPool, selectionSeed);
97116

98117
if (!selectedPromo || !selectedPromo.cta) {
99118
return null;

src/layouts/BaseLayout.astro

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ const shouldShowPromoBanner =
6161
>
6262
<header class="z-50 sticky left-0 right-0 top-0">
6363
<NavigationReact client:load currentURL={Astro.request.url} />
64-
{shouldShowPromoBanner && <PromoBanner client:only="react" />}
64+
{
65+
shouldShowPromoBanner && (
66+
<PromoBanner client:load requestPath={Astro.url.pathname} />
67+
)
68+
}
6569
</header>
6670

6771
<slot />

0 commit comments

Comments
 (0)