У вас же только один фон должен примениться по итогу, а вы в каждой итерации цикла там ещё и дефолтную картинку можете вставлять.
Ваш код (с минимальными измененями) должен выглядеть примерно так:
function shiftBackground(changes) {
let img = '/img/back/back.avif';
let scrollPosition = window.scrollY;
changes.forEach(([boxs, pathImg, _box]) => {
let shift = 0;
const _box_style = window.getComputedStyle(document.getElementById(_box), null);
const _box_margins = parseFloat(_box_style.marginTop) + parseFloat(_box_style.marginBottom);
const _box_height = document.getElementById(_box).offsetHeight + _box_margins;
boxs.forEach(box => {
const style = window.getComputedStyle(document.getElementById(box), null);
const margins = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
const height = document.getElementById(box).offsetHeight;
shift += height + margins;
});
if (scrollPosition >= shift && scrollPosition < shift + _box_height) {
img = pathImg;
}
})
const back = document.getElementById('back');
back.style.backgroundImage = `url(${img})`;
}
window.addEventListener('scroll', () => {
shiftBackground(
[
[['header', 'main'], '/img/back/back_2.jpg', 'about'],
[['header', 'main', 'scills_box', 'about'], '/img/back/back.jpg', 'contacts'],
]
);
});
P.S. Но вообще, скорее всего, это можно переписать на использование Intersection Observer. И код проще будет и производительность выше. Ну и не придётся руками перечислять предыдущие блоки. Но не очень вообще понятна требуемая логика: почему, во втором случае появился какой-то
scills_box между
main и
about...