Сделать плавающий блок React

Доброго времени суток!

Нужно сделать плавающий div с сообщением, без jQuery, чтобы при прокрутке ниже хедера это сообщение крепилось в верху экрана

Можно без рефов , на хуках, с функциональными компонентами, любыми способами только без jQuery.

Думал, что нужно взять координаты сообщения и когда верхние будут отрицательными добавлять css класс с position: fixed.

import React, { useState, useRef } from "react";
import "./styles.css";
import { observable, computed } from "mobx";
import { observer } from "mobx-react";

const Header = () => (
  <div className="header">
    <h1>Header</h1>
  </div>
);
const Message = React.forwardRef((props, ref) => {
  return (
    <div ref={ref} className="message">
      <h2>Message</h2>
    </div>
  );
});

const Space = () => <div className="space" />;

@observer
class App extends React.Component {
  constructor(props) {
    super(props);
    this.ref = React.createRef();
  }

  @observable sticky = this.ref.current.getBoundingClientRect();

  someMethod() {}

  render() {
    return (
      <div className="App">
        <Header />
        <Message ref={this.ref} />
        <button
          onClick={() => {
            console.log(this.sticky);
            this.ref.current.style.display = "flex";
          }}
        >
          add message
        </button>
        <button
          onClick={() => {
            console.log(this.sticky);
            this.ref.current.style.display = "none";
          }}
        >
          remove message
        </button>
        <Space />
        //получается сделать через событие
         <button
          onClick={() => {
            if (this.ref.current.getBoundingClientRect().top < 0) {
              this.ref.current.classList.add("sticky");
            }
          }}
        >
          add sticky
        </button>
      </div>
    );
  }
}

export default App;

css:

* {
  margin: 0;
  padding: 0;
}

.App {
  font-family: sans-serif;
  text-align: center;
}

.header {
  height: 100px;
  background: blue;
  display: flex;
  justify-content: center;
  align-items: center;
}

.space {
  height: 500px;
}

.message {
  display: none;
  background: green;
  height: 70px;
  justify-content: center;
  align-items: center;
}

.sticky {
  width: 100%;
  position: fixed;
  top: 0;
}

ссылка на CodeSandBox


Ответы (1 шт):

Автор решения: Vasily

Вручную делать sticky-элемент с помoщью JS довольно муторно и особого смысла не имеет так как большинство современных браузеров уже давно поддерживают position sticky:

div {
   position: sticky;
   top: 20px;
}

Элемент с липкой позицией — это элемент, у которого значение вычисленного position является sticky. Он рассматривается как относительно позиционированный до тех пор, пока содержащий его блок не пересечёт указанный порог (например, установка top в значение, отличное от auto) внутри его корня потока (или в контейнере, в котором он прокручивается), после чего он обрабатывается как «застрявший» до тех пор, пока не встретит противоположный край содержащего его блока.

Отвечая на Ваш вопрос:

// простой набросок подхода

const headerRef = useRef()    

const scrollHandler = () => {
   const bcr = headerRef.current.getBoundingClientRect()
   console.log(bcr)
}

useEffect(() => {
  window.addEventListner("scroll", scrollHandler, false)
  return () => window.removeEventListner("scroll", scrollHandler, false)
}, [])

return <div ref={headerRef} class="header"></div>

Метод getBoundingClientRect() в сочетании с событием onscroll даст вам возможность понять где именно в каждый конкретный момент находиться нужный вам элемент относительно viewport'a.

ВАЖНО: Вам нужно использовать debounce что бы ограничить количество срабатываний функции scrollHandler, иначе это приведет к просадке производительности Вашего приложения.

→ Ссылка