Исходный код
scrollEvent(e, element) {
const elementRect = element.getBoundingClientRect();
const elemenParenttRect = element.parentElement.getBoundingClientRect();
if (document.documentElement.scrollTop <= elementRect.top) return;
if (this.point < document.documentElement.scrollTop) {
if (-1 * elemenParenttRect.top + window.innerHeight >= elemenParenttRect.height) {
this.hookTop = true;
element.style.position = 'absolute';
element.style.transform = `translateY(${-1 * elemenParenttRect.top - -1 * elementRect.top}px)`;
element.style.left = `0px`;
return;
}
if (
(element.getBoundingClientRect().top - window.innerHeight) * -1 > element.getBoundingClientRect().height + 30 &&
element.getBoundingClientRect().height > window.innerHeight
) {
element.style.left = `${elementRect.x}px`;
element.style.transform = `translateY(${elementRect.top}px)`;
element.style.position = 'fixed';
this.hookTop = false;
}
if (element.getBoundingClientRect().height < window.innerHeight && elemenParenttRect.top <= 0) {
element.style.position = 'fixed';
element.style.transform = `translateY(0px)`;
element.style.left = `${elementRect.x}px`;
}
if (this.hookTop && element.getBoundingClientRect().height > window.innerHeight) {
element.style.left = `0px`;
element.style.position = 'absolute';
element.style.transform = `translateY(${-1 * elemenParenttRect.top - -1 * elementRect.top}px)`;
}
} elseif (!this.wasTop) {
element.style.position = 'absolute';
element.style.transform = `translateY(${-1 * elemenParenttRect.top - -1 * elementRect.top}px)`;
element.style.left = `0px`;
if (elementRect.top >= 0) {
this.hookTop = true;
element.style.position = 'fixed';
element.style.transform = `translateY(0px)`;
element.style.left = `${elementRect.x}px`;
}
if (elemenParenttRect.top >= 0) {
element.style.position = 'static';
element.style.transform = `translateY(none)`;
element.style.left = `${elementRect.x}px`;
this.hookTop = false;
}
}
this.point = document.documentElement.scrollTop;
}
Исходный код предназначен для управления плавающим элементом на веб-странице: при определённых условиях он «заливает», при других — скроллится вместе с контентом страницы.
Что не так в исходном коде
Главный недостаток исходного кода — смешение ответственностей (знаний) разного уровня в одном методе. Тут и вычисление положения, в котором должен оказаться блок при каждом положении скроллинга, и все детали позиционирования.
Как можно сделать лучше
В результате рефакторинга были не только разделены ответственности, но и заметно упрощён алгоритм:
onScroll() {
const windowHeight = this.windowHeight = window.innerHeight;
const interRect = this.interTarget.getBoundingClientRect();
const interTop = interRect.top;
const interBottom = interRect.top + interRect.height;
if (interTop > windowHeight + 500) {
this.hide();
}
else if (interBottom < -500) {
this.hide();
}
else {
this.setAsFloatingWithInter(interTop);
}
}
hide() {
this.stickyTarget.style = 'visibility: hidden;';
}
setAsFloatingWithInter(stickyTop) {
stickyTop = Math.min(stickyTop, 0);
stickyTop = Math.max(stickyTop, this.windowHeight - this.stickyHeight);
this.stickyTarget.style = `visibility: visible; position: fixed; top: ${ stickyTop }px`;
}
Самое важное изменение — появились новые методы, которые «знают», как правильно позиционировать плавающий блок в каждой ситуации, а исходный метод занимается только определением того, какая именно ситуация сейчас имеет место быть.