Borrow/return system logic in JS [closed]

Users will also be able to borrow books from the library. Each borrowed book will have a loan date and a return date. When there is only one day left before the return date, the system must send the user an email reminder and also show them a notification on the website. If the book is not returned on time, the Admin will receive a notification message about the overdue book, and the Admin can place restrictions on that user

How to write this logic in JavaScript

Regex replace with /g/ is replacing the value twice

I have a simple regex replace – user provides find string and replace string. I’m using originalString.replace(new RegExp(find, 'gi'), replace) and it’s been working mostly fine. Recently I noticed that one of the edge cases where the replace string is applied twice when the find string is a match all.

For example:

const first =   'bat, batman, manbat'.replace(new RegExp('bat', 'gi'), 'cat');
console.log('first: ', first);
// => 'cat, catman, mancat'  // Every bat is replaced with cat, thanks to the global switch


const second = "bat, batman, manbat".replace(new RegExp('(.*)', 'gi'), '$1, cat');
console.log('second: ', second);
// => 'bat, batman, manbat, cat, cat'  // here, the 'cat' is replaced twice because of 'g'

Why is the ‘cat’ being applied twice for the second result? I would have expected it to just be bat, batman, manbat, cat – I just want to add , cat to multiple instances of strings, but they all have , cat twice at the end now.

The problem goes away in the second case if I take away the global switch, but then the first case would fail. How to fix this?

Line with curves on each side creates background problems?

I am trying to create a line that “flows” from the left side of the screen to the right side with curves at the sides, will post a pictures for clarification. I however don’t know a native way to create an upwards/inwards curving line on the bottom of a div. This creates the problem that I still need everything above the line to be black so the black div to the left named (.start) is needed to make the background black and then a div below it to cover the black and make the line curve with a simple border radius.

This interferes with any background below the line that is not a plain color, like an image or such, since the cover block is not transparent.

What I am trying to create, with the ability to place images and such below it

Current problem, see how the cover piece sticks down a bit into the grey. Ignore how the text is cut off that is simply solved with a transparent background on the container, but its covering the black as of now.

Different colors on all the divs to see how this is built.

Different colors and with the cover piece transparent.

Snippet too see it in action:

      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      html,
      body {
        overflow-x: hidden;
      }

      .container {
        width: 100%;
        height: 200px;
        display: flex;
        background-color: white;
        position: relative;
      }

      .start {
        width: calc(100px - 2rem);
        height: calc(200px - 2rem);
        background-color: black;
      }
      .start-cover {
        width: 120px;
        height: 110px;
        background-color: white;
        position: absolute;
        left: -2.9rem;
        bottom: -1px;
        border-left: 1.2rem solid #ffff00;
        border-top: 1.2rem solid #ffff00;
        border-radius: 100% 0 0;
      }
      .mid {
        width: calc(100% - 200px + 4rem);
        height: 110px;
        background-color: black;
        border-bottom: 1.2rem solid #ffff00;
      }
      .end {
        width: 120px;
        height: 110px;
        position: absolute;
        top: 0;
        right: -2.8rem;
        background-color: black;
        border-bottom: 1.2rem solid #ffff00;
        border-right: 1.2rem solid #ffff00;
        border-radius: 0 0 100%;
      }
    <div class="container">
      <div class="start"></div>
      <div class="mid"></div>
      <div class="end"></div>
      <div class="start-cover"></div>
    </div>

I have tried switching the divs with both a PNG and SVG but none worked as good as I want them to with responsibility. And therefore I’ve stuck with this solution.
If you have a alternative solution feel free to help me but also if it is just a simple fix I haven’t thought of.

How to fix Strapi 5 “middlewares” environment variables not being read when deploying to fly.io through GitHub Actions?

I have a basic Strapi 5 project. Inside I configured config/middlewares.ts like this:

export default ({ env }) => [
  {
    name: "strapi::cors",
    config: {
      origin: env("CORS_ORIGIN").split(","),
      methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
      headers: ["Content-Type", "Authorization", "Origin", "Accept"],
      keepHeaderOnError: true,
    },
  },
...

On fly.io I configured this environment variable along with
a bunch of others. I also have this GitHub Action:

name: Fly Deploy
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: Deploy app
    runs-on: ubuntu-latest
    concurrency: deploy-group    # optional: ensure only one action runs at a time
    steps:
      - uses: actions/checkout@v4
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

Which runs this Dockerfile on the fly.io builder:

FROM node:22-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
COPY . /app
WORKDIR /app

FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build

FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 1337
CMD [ "pnpm", "start" ]

For some reason, every environment variable I am loading in a similair way in other config files are read properly, and even the one in my middleware is loaded locally. My GitHub Action reports an issue from the remote fly.io builder however:

Error: Could not load js config file /app/dist/config/middlewares.js:      ││   Cannot read properties of undefined (reading 'split')

Menu with submenus – The hardware back button takes to first submenu page clicked on instead of menu page

Just started learning React Native (using expo)
I have a profile.jsx in my app folder. It has multiple sub-menu. Each sub-menu also has a custom “<- Go Back” button which takes to profile.jsx.

File structure:

folder app has:

  1. index.jsx

  2. profile.jsx

  3. home.jsx

  4. (auth) folder

  5. (profileMenu) folder

  6. (category) folder

  7. product folder

(auth) has _layout, signIn, login, guest and register jsx files

(profileMenu) has _layout, Favourites, Wallet, Payment, Settings, etc. also all jsx files.

In profile.jsx I have clickable menu items of (profileMenu) which on click go to that particular jsx file.

The <- go back button inside the files themselves work as intended but when using android Hardware back button the page goes back to through all the profileMenu items I have clicked through instead of going back to the default page which is profile.jsx.

Then further once I am on profile.jsx and I click on another profileMenu option (other than the first clicked option) and use the HW back button it takes me to that first clicked file instead of profile.jsx.

I have tried both router.push() and router.replace(). Both show the same bug.

BUT I have a menu item this does not happen with- Logout. I click on it any number of items it does not show this bug. If I click on first, go back and click on another menu item, I am successfully able to go back to profile and the logout page does not appear.

My logout button takes me to another folder (auth) which contains a few other files like signIn.jsx guest.jsx. In all of these files the HW back button takes to the previous page like it should

I will attach the necessary snippets from all the relevant files and also attach an image of my file structure. Please do help me.

/app/ _layout.jsx:

import { Ionicons } from "@expo/vector-icons";

export default function Layout() {
  return (
    <Tabs
      screenOptions={{
        headerShown: false,
      }}
    >
      <Tabs.Screen
        name="profile"
        options={{
          title: "Profile",
        }}
      />

      <Tabs.Screen
        name="(auth)"
        options={{
          href: null,
        }}
      />
      <Tabs.Screen
        name="(profileMenu)"
        options={{
          href: null,
        }}
      />
    </Tabs>
  );
}

profile.jsx ->

//other imports etc.

const profile = () => {

  const router = useRouter();

  const handleMenuPress = (label) => {
  const menuItems = [
  
    { icon: 'percent', label: 'Promotions',  },
    { icon: 'settings',label: 'Settings', },
    { icon: 'log-out', label: 'Logout', },
  ];
    console.log(`Pressed: ${label}`);

    switch (label) {
      case 'Promotions':
        router.push('/(profileMenu)/Promotions');
        break;
      case 'Settings':
        router.push('/(profileMenu)/Settings');
        break;
      case 'Logout':
        router.replace('/(auth)/signIn');
        break;
      default:
        console.log('Unknown menu item');
    }
  }
  return (

    //other profile options line name etc...


      {/* Menu Items */}
      <ScrollView>
        <View>
          {menuItems.map((item, index) => {
            const IconComponent = item.iconSet;
            return (
              <TouchableOpacity
                key={index}
                onPress={() => handleMenuPress(item.label)}
              >
                <Text>{item.label}</Text>
              </TouchableOpacity>
            );
          })}
        </View>
      </ScrollView>
);
};


;
export default profile

Promotions.jsx:

//imports 
import { useRouter } from 'expo-router';

const Promotions = () => {
  const router = useRouter();

  const navigateToProfile = () => {
    router.replace('/profile');
  };
  return (
    <View>
      <View>
        <TouchableOpacity
          onPress={navigateToProfile}
        >
          <Text style={styles.backButtonText}>← Back</Text>
        </TouchableOpacity>
        <Text style={styles.title}>Payments</Text>
      </View>
      <Text>Promotions</Text>
    </View>
  )
}

export default Promotions

Settings.jsx:

//imports plus :
import { useRouter } from 'expo-router';


const Settings = () => {
  const router = useRouter();

  const navigateToProfile = () => {
    router.replace('/profile');
  };
  return (
    <View>
      <View>
        <TouchableOpacity
          onPress={navigateToProfile}
        >
          <Text>← Back</Text>
        </TouchableOpacity>
        <Text>Settings </Text>
      </View>
      <Text>
        Settings
      </Text>
    </View>
  )
}

export default Settings

in (profileMenu) folder: _layout.jsx:

import { Stack } from 'expo-router';

export default function ProfileLayout() {
  return (
    <Stack
      screenOptions={{
        headerShown: false,
      }}
    >
      <Stack.Screen name="Promotions" />
      <Stack.Screen name="Settings" />
      <Stack.Screen name="Wallet" />
    </Stack>
  );
}

(auth) _layout.jsx

export default function AuthLayout() {
  return (
    <Stack
      screenOptions={{
        headerShown: false,
      }}
    >
      <Stack.Screen name="signIn" />
      <Stack.Screen name="login" />
      <Stack.Screen name="register" />
      <Stack.Screen name="guest" />
    </Stack>
  );
}

signIn.jsx:

//imports
import { router } from 'expo-router';


const { width, height } = Dimensions.get('window');

const SignIn = () => {

    const menuItems = [
        { id: 1, label: 'login', text: 'Login to Your Account' },
        { id: 2, label: 'register', text: 'Create an Accound' },
        { id: 3, label: 'guest' },
    ]
    const handleMenuPress = (label) => {
        if (label === 'login') {
            router.push('/(auth)/login');
        } else if (label === 'register') {
            router.push('/(auth)/register');
        } else if (label === 'guest') {
            router.push('/(auth)/guest');
        }
    };

    return (
        <SafeAreaView>

            <ScrollView >
                <View >
                    <Text >Sign in to continue shopping</Text>
                </View>

                <View >
                    <View>

                        <TouchableOpacity
                            onPress={() => handleMenuPress(menuItems[0].label)} 
                        >
                            <Text>{menuItems[0].text}</Text>
                        </TouchableOpacity>

                        <TouchableOpacity
                            onPress={() => handleMenuPress(menuItems[1].label)}
                        >
                            <Text>{menuItems[1].text}</Text>
                        </TouchableOpacity>
                    </View>


                    <TouchableOpacity
                       
                        onPress={() => handleMenuPress('guest')}
                    >
                        <Text >Continue as Guest</Text>
                    </TouchableOpacity>
                </View>

            </ScrollView>
        </SafeAreaView>
    )
}

export default SignIn;

File Structure:

I have tried replacing the whole file twice, I have tried using router.push() as well as router.replace(). Both of them did not work. But for some reason logout menu item works.

I have replaced the _layout.jsx in both (auth) and (profileMenu) twice.
I am unsure at this point why my HardWare back Button is not functioning correctly.

How to get contentWindow of iframe without mounting to DOM

I’m working on a React app where I need to manage multiple iframes dynamically. Each iframe is created only once and stored in a cache (Map/Ref) for reuse. At any given time, only the selected iframe should be mounted in the DOM. Other iframes remain cached (not attached to the DOM).

My question is:
Is there a way to send messages/events (using postMessage or similar) to an iframe that is stored in cache but not currently mounted in the DOM? Currently i’m getting contentWindow value as null

Example workflow:

  1. Create iframe for itemId = 1, store it in cache.
  2. Switch to itemId = 2 → mount its iframe, keep itemId = 1 iframe in cache (not in DOM).
  3. From itemId = 2, I want to send a message/event to the iframe of itemId = 1.

Is this possible? Anyway?

DHTMLX Gantt – Internal server error when exporting

I’ve inherited a project that uses DHTMLX Gantt charts, and I was trying to understand why the export was not working as expected.

To achieve this, I recreated a small application to test the basics, however, here I am already running into an issue.

When trying to export a fairly simple project, with 3 sub-tasks, calling the export method gives me a beautiful black screen, stating “Internal server error. Error: Gantt to Excel. TypeError: Cannot read properties of undefined (reading ‘forEach’)

Here is the snippet:
https://snippet.dhtmlx.com/28horkya

Any help in understanding this is greatly appreciated.

Configuring ConsoleNinja to run in VSCode

I have installed ConsoleNinja extention in VSCode.

In powershell terminal, i run index.js file using node index.js I get the output of my index.js file but don’t see console.log message values I am supposed to see.

In the status bar I can see ConsoleNinja icon with the following message:

enter image description here

also I get the following in ConsoleNinja log file:

20:49:53.483 WARN host No clients connected

20:49:54.076 info buildHook-11924 allowed tools: vite, angular, jest, webpack, next.js, nest.js, cypress, http-server, serve, live-server, nuxt, remix, qwik, hydrogen, serverless, astro, live-server-extension, live-preview-extension, node

20:49:54.076 info buildHook-11924 running tools: live-preview-extension

20:49:54.083 info buildHook-11924 installing fs interceptor

20:49:54.082 info host client connected: buildHook { address: ‘127.0.0.1’, family: ‘IPv4’, port: 59380 }

20:49:54.131 info buildHook-11924 file processed (stat): 2 log points, error handler: false, network logging handler: false

20:49:54.140 info buildHook-11924 file processed (stat): 1 log points, error handler: false, network logging handler: false

20:49:54.158 info buildHook-11924 file processed (stat): 0 log points, error handler: false, network logging handler: false

20:49:54.198 info buildHook-11924 file processed (stat): 0 log points, error handler: false, network logging handler: false

20:49:54.231 info buildHook-11924 file processed (stat): 0 log points, error handler: false, network logging handler: false

20:49:54.246 info buildHook-11924 file processed (stat): 0 log points, error handler: false, network logging handler: false

20:49:54.250 info buildHook-11924 allowed tools: vite, angular, jest, webpack, next.js, nest.js, cypress, http-server, serve, live-server, nuxt, remix, qwik, hydrogen, serverless, astro, live-server-extension, live-preview-extension, node

20:49:54.250 info buildHook-11924 running tools: live-preview-extension

Also from the terminal running this command gives an error:

npx console-ninja run index.js

npx : File C:Program Filesnodejsnpx.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see
At line:1 char:1

  • npx console-ninja run index.js

ConsoleNinja Settings in VSCode:

enter image description here

CSS/JS Animation not playing in some browsers

I’m using this javascript code to play two different kinds of animations (a “fade” and a “wipe”) when an element is visible. Everything works as intended in Firefox, but when I test it in Chrome/Edge, the “wipe” doesn’t work.

const options = {
  root: null, // null means you want to observe changes relative to the viewport
  threshold: 0.1, // Trigger when 10% of the element is visible
  rootMargin: '0px'
};

const callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // Element is in view, add the animation
      entry.target.classList.add('animate');
    }
  });
};

const observer = new IntersectionObserver(callback, options);

const elements = document.querySelectorAll('.animate-fade, .animate-wipe');
elements.forEach(element => {
  observer.observe(element);
});
.animate-fade,
.animate-wipe {
  transition-timing-function: ease-out;
  transition-duration: 1.7s;
}

.animate-fade {
  opacity: 0;
}

.animate-wipe {
  clip-path: rect(0px 0% 100% 0px);
}

.animate {
  opacity: 1;
  clip-path: rect(0px 100% 100% 0px);
}
<div class="animate-fade">
  <p>This text has a fade effect and seems to work across browsers :).</p>
</div>
<div class="animate-wipe">
  <p>This text has a wipe effect and seems to only work on some browsers :(.</p>
</div>

How do I fix this?

How to preserve screen reader reading position when switching sections?

I’m building a React web application for blind users (screen reader users). The goal is to let users read a document while also being able to ask an AI chatbot questions at any time.

Intended flow:

  1. The user reads through the document with their screen reader.
  2. When they have a question, they switch to the chat section to ask it
    and listen to the answer.
  3. They return to the document and should ideally continue from the
    same spot they left off.

I’m unsure how to best preserve this reading position when switching between the two sections.

I’ve considered:

Option 1: Using clear headings, landmarks, or adding IDs for each paragraph so users can navigate back manually.

Question: Does this place too much cognitive demand on the user? What should I watch out for if I do it this way?

Option 2: On top of Option 1, also providing a “Return to where you left off” button or bookmark system.

Question: From my research, screen readers use a virtual cursor in “browse mode” so web developers cannot capture their screenreader cursor position directly, is such a feature actually doable? If so, how should I get their reading position?

Are there any other solutions?

In general, I would like to know:

Is this web design generally in line with accessibility guidelines?

What are the best practices in web development to help screen reader users resume reading from the same spot after switching to another interactive area?

why CSS mix-blend-mode: difference not working with Spline 3D background element

I’m trying to implement a mix-blend-mode: difference effect on header text that should blend with a 3D Spline background, but it’s not working. The navigation bar has the same blend mode and works perfectly, but the header text doesn’t show any color inversion.

this is my current setup:

  • Using Spline 3D background (<spline-viewer> element)

  • Navigation bar has mix-blend-mode: difference and works correctly

  • Header text has mix-blend-mode: difference but shows no effect

  • Both are positioned outside the main content to avoid stacking context issues

here the html code block:

<!-- 3D BACKGROUND -->
<spline-viewer id="spline-background" url="..."></spline-viewer>

<!-- NAVIGATION (working blend mode) -->
<div id="navigationbar">
    <span class="nav-name">Anubhab</span>
    <div class="nav-links">...</div>
</div>

<!-- HEADER TEXT (blend mode not working) -->
<div id="header">
    <h1>I am a Developer specializing in...</h1>
</div>

<!-- MAIN CONTENT -->
<main id="main-content">...</main>


here the css code block:


#spline-background { 
    position: fixed; 
    top: 0; 
    left: 0; 
    width: 100%; 
    height: 100%; 
    z-index: -2;
    pointer-events: none; 
}


#navigationbar { 
    position: fixed; 
    top: 0; 
    left: 0; 
    width: 100%; 
    z-index: 100; 
    mix-blend-mode: difference; /* This works */
}

#header {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 0;
}

#header h1 { 
    color: rgba(255, 255, 255, 0.75); 
    mix-blend-mode: difference; /* This doesn't work */
    z-index: -1;
}

full code here :
html, css, js

  1. Moved header outside main content to avoid opacity transitions

  2. Adjusted z-index values to ensure proper layering

  3. Removed isolation: isolate properties

  4. Added hardware acceleration (will-change: transform, transform: translateZ(0))

  5. Tried different blend modes (exclusion, multiply)

Header text should invert colors (white to black) when overlapping with dark parts of the 3D background, similar to how the navigation bar works.

Afterall:

Header text remains white without any blend effect, even when overlapping the 3D background elements.

Browser: Chrome (latest), Windows 11

Why is mix-blend-mode: difference working for the navigation bar but not for the header text, despite having similar positioning and CSS properties? Is there something specific about Spline 3D elements that prevents blend modes from working properly?

Inkscape SVG misaligned when put into React+Vite project and won’t rotate around hinge

I am making a microfuge tube that has two groups: the cap and the body. I used Inkscape to make the microfuge tube, and then I ran the XML file through Chat GPT to get a simplified version of the SVG file. However, when I import it into my program, the cap is not aligned with the body, and the cap doesn’t rotate around the hinge.Image of the microfuge tube in Inkscape. Image of the microfuge tube in React+Vite.

Attached below is the way I use it. Basically, I want the microfuge tube to switch between two states, one with the cap on and one with the cap off. But, I just can’t get the cap to align with the body, and it’s beyond me how to rotate it around the specific hinge.

export default function MicrofugeTube({ capOff = false }) {
  return (
    <svg
      viewBox="0 0 800 600"
      width={300}
      height={225}
      xmlns="http://www.w3.org/2000/svg"
    >
      {/* Animated Cap */}
      <motion.g
  transform="translate(110, -10000)" // adjust until hinge sits on neck
  style={{ transformOrigin: "85px 152px", transformBox: "fill-box" }}
  animate={{ rotate: capOff ? -120 : 0 }}
  transition={{ duration: 0.8, ease: "easeInOut" }}
>
  <path
    d="M84.8,151.7c0,3.06667,0,6.13333,0,9.2
      c22.17767-0.0983,44.38367,0.0623,66.54365-0.1976
      c3.18545-0.7008,6.3709-1.4016,9.55635-2.1024
      c0.71378-3.61798-1.47148-4.71625-4.73651-4.87603
      c-4.4144-1.35434-8.98911-1.38782-13.56349-1.42397
      c-0.0333-6.13333-0.0667-12.26667-0.1-18.4
      c-13.2-0.0667-26.4-0.13333-39.6-0.2
      c0,6,0,12,0,18c-6.033333,0-12.066667,0-18.1,0z"
    fill="#5b88b4"
    fillOpacity={0.5}
  />
</motion.g>
      {/* Tube Body */}
      <path
        d="M196.2,134.1v0.2z
          M196.2,134.1c-9.27074,0.38254-18.61158,0.25031-27.83401,1.07853
          c-2.3981,0.82584-6.14016,0.64247-7.73115,2.07439
          c-0.89721,3.74244,2.19367,4.14387,5.10309,4.69733
          c2.54792,0.59357,5.05574,1.31273,7.46207,2.34975
          c0.21053,26.36438,0.11861,52.74924,0.51779,79.10085
          c2.89407,22.13305,5.78814,44.2661,8.68221,66.39915
          c2.3272,2.15245,3.58971,6.21119,7.29718,5.89232
          c5.2662,0.78805,10.66732,0.53642,15.90282-0.39232
          c2.15558-2.83047,5.44348-5.16439,5.07117-9.10341
          c2.73994-21.5376,5.78218-43.05474,8.33348-64.6051
          c0.13178-25.76383,0.26357-51.52766,0.39535-77.29149
          c3.6118-0.24173,3.81689-2.82813,3.4-5.84261
          c1.24809-4.02448-1.97453-3.72759-4.99398-3.62709
          C210.60403,134.58642,203.40218,134.33825,196.2,134.1z"
        fill="#5b88b4"
        fillOpacity={0.5}
      />
    </svg>
  );
}

I’ve tried to change the translate(110,-10000), but there is no noticeable effect even when I go to extremes. I’ve also tried to change the viewBox, but that doesn’t do anything except make it smaller or larger.

How can I have intellisense in VS Code for opencv.js

I want to have intellisense for opencv.js inside VS Code when I call it from a JavaScript file, but when I use import "path/opencv.js" or import cv from "path/opencv.js", I can’t have intellisense for cv.

In the OpenCV documentation, there are following description and a example:

Once opencv.js is ready, you can access OpenCV objects and functions through cv object. The promise-typed cv object should be unwrap with await operator. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await .

imgElement.onload = await function() {
  cv = (cv instanceof Promise) ? await cv : cv;
  let mat = cv.imread(imgElement);`
}

but the intellisense doesn’t even show a promise-typed cv.

It is worth mentioning that my environment is browser.

`window.scrollTo` scrolls outside the container on iOS Safari

I’m running into an issue with window.scrollTo on iOS Safari.

On desktop browsers (Chrome/Firefox/Edge), this works as expected:

window.scrollTo(0, document.body.scrollHeight);

Even when resizing the window, the scroll goes correctly to the bottom of the page.

But on iOS Safari, the behavior is different:

  • It scrolls past the bottom of my main container (section#section).
  • The end of the <section> gets aligned at the top of the viewport.
  • I then need to manually scroll a little bit up/down before the browser “fixes” the scroll position (as if Safari realizes it scrolled outside the valid range).

Here’s the simplified HTML/CSS:

<html>
<head>
<style type="text/css">
body {
    height: 100vh;
}
section#section {
    height: auto;
}
</style>
</head>
<body>
<section id="section">
    [...]
</section>
</body>
</html>

Things I’ve already tried:

  • window.scrollTo(0, document.body.scrollHeight);

  • Scrolling the section itself:

    section.scrollTo({
        top: section.scrollHeight,
        behavior: 'smooth',
    });
    
  • Wrapping the scroll in a setTimeout (50ms delay).

Unfortunately, none of these fix the issue on iOS Safari.

Has anyone encountered this behavior before, or knows a reliable workaround to make sure the scroll goes correctly to the bottom on mobile Safari?

Thanks!