Возможно это поможет.
Не оптимизировано, но логика, надеюсь, будет ясна.
1. Собираешь все свои секции за которыми надо следить.
2. Вешаешь обработчик события на скролл ноду - в нашем случае window, но можно докрутить и до контенра на странице с внутренним скролом. Но в таком случае надо будет расчитывать координаты относительно родителя.
3. При каждом скролле проходишь по массиву секций и выясняешь зашла ли она за указанное в offset расстояние по вертикали (сверху 0), для примера середина экрана window.innerHeight/2, и не поднялся ли низ секции из того же офсета (некоторые браузеры могут ругаться на отсутсвующее свойство .bottom в результате getBoundingClientRect(), поэтому в примере bb.top + bb.height). Это нужно для деактивации пункта навигации - одна зашла - другие вышли.
4. Цепляешь класс .active если секция вошла по data-id => data-nav-id если его нет еще или снимаешь его если он есть.
Обязательно нужен дебаунс на скролле хотя бы в 100ms.