Вопрос или проблема
У меня есть массив, и я хочу, чтобы, если на экране видно 4 элемента, он возвращал верхний элемент, видимый на экране.
Вы можете ознакомиться с этим видео, чтобы лучше понять интерфейс и проблему.
Текущая настройка:
const ActionResponseAndRequest = ({actions, info, openapi, serverurl, RequestMethods, globalUrl}) => {
const [ExampleBody, setExampleBody] = useState(actions[0]?.body);
const [apiResponse, setApiResponse] = useState({});
const [filteredActions, setFilteredActions] = useState([]);
useEffect(() => {
if (actions?.length > 0 && filteredActions?.length === 0) {
setFilteredActions(actions.slice(0, loadAction));
}
}, [actions?.length]);
return(
<div style={{ paddingTop: 20, display: "flex", flexDirection: "column", gap: "10px" }}>
{filteredActions.map((action, index) => (
<div key={index} style={{ width: "100%", boxSizing: "border-box" }}>
<Action
id={action.name.replace(/ /g, "-").replace(/_/g, "-")}
action={action}
index={index}
serverurl={serverurl}
setApiResponse={setApiResponse}
setExampleBody={setExampleBody}
globalUrl={globalUrl}
info={info}
RequestMethods={RequestMethods}
/>
</div>
))}
</div>
)}
const Action = (
{
action,
index,
serverurl,
setApiResponse,
setExampleBody,
globalUrl,
info,
RequestMethods,
},
) => {
const actionRef = useRef(null);
const scrollTimeoutRef = useRef(null);
const [currentActionIndex, setCurrentActionIndex] = useState(0);
useEffect(() => {
const observer = new IntersectionObserver(
throttle((entries) => {
let nextSelectedActionIndex = null;
entries.forEach((entry) => {
if (entry.isIntersecting && currentActionIndex !== index) {
nextSelectedActionIndex = index;
}
});
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
if (nextSelectedActionIndex !== null) {
scrollTimeoutRef.current = setTimeout(() => {
// Установить индекс выбранного действия только в том случае, если он изменился
if (currentActionIndex !== nextSelectedActionIndex) {
const actionId = action.name.replace(/ /g, "-").replace(/_/g, "-");
window.history.pushState(null, "", `#${actionId}`);
setExampleBody(action?.example_response);
setCurrentActionIndex(nextSelectedActionIndex)
}
}, 300);
}
}, 200),
{ threshold: 0.5 }
);
if (actionRef.current) {
observer.observe(actionRef.current);
}
return () => {
if (actionRef.current) {
observer.unobserve(actionRef.current);
}
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
};
}, [index, action]);
useEffect(() => {
if (currentActionIndex !== null) {
const element = document.getElementById(`action-list-${currentActionIndex}`);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "center" });
}
}
}, [currentActionIndex]);
return (
<div
ref={actionRef}
id={action.name.replace(/ /g, "-").replace(/_/g, "-")}
data-index={index}
key={action.name.replace(/ /g, "-").replace(/_/g, "-")}
style={{
display: "flex",
flexDirection: "row",
minHeight: 400,
borderBottom: "1px solid #494949",
marginBottom: 30,
justifyContent: 'center'
}}
>
{/*Код действия */}
</div>
)}
Ожидаемый результат:
Предположим, что на экране видно два или более действий одновременно, тогда он должен установить путь URL на имя текущего действия. Например, если на экране видно 4 элемента с индексами от 0 до 3, то текущий видимый элемент должен быть элементом с индексом 0, и если я прокручу вниз и элемент с индексом 0 не будет виден на экране, то он должен установить текущий видимый элемент на индекс 1. Как я могу добиться такого поведения?
Я пытался добиться этого поведения, используя IntersectionObserver
, но текущий код возвращает последний элемент, видимый на экране. Также, когда я прокручиваю вниз и прокручиваю вверх, он не устанавливает хэш-URL окна на первый видимый элемент, это абсолютно запутано. На маленьком экране это не работает.
Ответ или решение
Чтобы добиться нужного поведения в React.js, где действующий компонент отслеживает и возвращает верхний видимый элемент из списка компонентов, мы можем использовать IntersectionObserver
. Давайте рассмотрим, как это можно реализовать на основе вашего текущего кода.
1. Основная структура кода
Мы создадим компонент, который визуализирует список элементов и отслеживает, какой из них находится в верхней части видимой области (viewport). Ваши текущие настройки и использование хуков React хороши, но потребуются небольшие улучшения, чтобы правильно отслеживать верхний видимый элемент.
2. Использование IntersectionObserver
Ваш текущий код создает экземпляр IntersectionObserver
, но необходимо убедиться, что он правильно отслеживает верхний элемент. Мы будем использовать массив для хранения всех элементов, которые пересекают границы видимой области. Затем мы сможем определить, какой из них является верхним.
3. Новый код
Вот пример кода, который может решить вашу задачу:
const ActionResponseAndRequest = ({actions, info, openapi, serverurl, RequestMethods, globalUrl}) => {
const [ExampleBody, setExampleBody] = useState(actions[0]?.body);
const [apiResponse, setApiResponse] = useState({});
const [filteredActions, setFilteredActions] = useState([]);
const [currentActionIndex, setCurrentActionIndex] = useState(-1); // Для отслеживания текущего индекса
useEffect(() => {
if (actions?.length > 0 && filteredActions.length === 0) {
setFilteredActions(actions.slice(0, 4)); // Загружаем 4 элемента
}
}, [actions]);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
let visibleEntries = entries.filter(entry => entry.isIntersecting);
if (visibleEntries.length > 0) {
// Сортируем элементы по их позиции на экране
visibleEntries.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
const topVisibleEntry = visibleEntries[0]; // Получаем верхний элемент
const index = Array.from(document.querySelectorAll('.action')).indexOf(topVisibleEntry.target);
if (index !== -1 && currentActionIndex !== index) {
const actionId = filteredActions[index].name.replace(/ /g, "-").replace(/_/g, "-");
window.history.pushState(null, "", `#${actionId}`);
setExampleBody(filteredActions[index].example_response);
setCurrentActionIndex(index); // Обновляем текущий индекс
}
}
},
{ threshold: 0.5 }
);
const items = document.querySelectorAll('.action');
items.forEach(item => observer.observe(item)); // Наблюдаем за каждым элементом
return () => {
items.forEach(item => observer.unobserve(item)); // Развязываем наблюдателя
};
}, [filteredActions, currentActionIndex]);
return (
<div style={{ paddingTop: 20, display: "flex", flexDirection: "column", gap: "10px" }}>
{filteredActions.map((action, index) => (
<div key={index} className="action" style={{ width: "100%", boxSizing: "border-box" }}>
<Action
id={action.name.replace(/ /g, "-").replace(/_/g, "-")}
action={action}
index={index}
serverurl={serverurl}
setApiResponse={setApiResponse}
setExampleBody={setExampleBody}
globalUrl={globalUrl}
info={info}
RequestMethods={RequestMethods}
/>
</div>
))}
</div>
);
};
const Action = ({ action, index }) => {
// ваш код компонента Action
return (
<div
className="action-item" // Добавление класса для удобства
style={{
display: "flex",
flexDirection: "row",
minHeight: 400,
borderBottom: "1px solid #494949",
marginBottom: 30,
justifyContent: 'center'
}}
>
{/* Отображение вашего компонента Action */}
</div>
);
};
4. Пояснения к изменениям
- Отслеживание индекса: Используем переменную
currentActionIndex
для отслеживания индекса активного элемента. - Сортировка видимых элементов: В
IntersectionObserver
мы фильтруем элементы, которые находятся в видимой области, а затем сортируем их по вертикальной позиции на экране. Это позволяет выбрать верхний элемент. - Управление URL: При обнаружении нового верхнего элемента мы обновляем URL, чтобы отразить это изменение.
Заключение
С помощью этих изменений ваше приложение должно правильно отслеживать верхний видимый элемент и обновлять URL в зависимости от текущего состояния прокрутки. Не забудьте протестировать на разных экранах, чтобы гарантировать должную работу. Использование IntersectionObserver
позволило эффективно управлять наблюдением за элементами, и этот подход должен уменьшить нагрузку на производительность, так как он не требует постоянного отслеживания на каждом изменении прокрутки.