23

5 Steps: Create a Size & Position Observer in React

 5 years ago
source link: https://www.tuicool.com/articles/hit/iUfINb3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

5 Steps: Create a Size & Position Observer in React

Have you ever thought about why we have to inspect every time we want to know the width or height of the component? For me, it is really painful and time wasting.

If there are a number of width and height popup on the screen and automatically update when the size changed, It would be absolutely awesome.

Well, that’s why I’m here today. I want to share with you how to create size & position observer in React, using “Render Props” and “Higher Order Component” techniques .

Tip: When working with React components, use Bit to organize and share them in multiple applications and projects- to build faster! Give it a try.

Demo

Prerequisite

a bit of understanding of these topics.

Get Started

open CodeSandbox in browser, signup if you don’t have an account. It is an amazing tool for prototyping and testing web application.

Step 0

Vvmque7.png!web

Click create sandbox then choose “React”. It is the same template from the official create-react-app repo.

Add dependencies for styling components. I use react-emotion because it is very easy to write css.

32Q7bqZ.png!web

Click Add Dependency and then search for

  • emotion
  • react-emotion
  • cuid

Step 1

uYZNF3A.png!web

Right click at src folder and create file named SizeObserver.js then create simple component that render children

import React from "react";
class SizeObserver extends React.Component {
  render() {
    return this.prop.children;
  }
}
export default SizeObserver;

Step 2

add constructor and method getBound()

class SizeObserver extends React.Component {
  constructor(props) {
    super(props);
    this.id = props.name;
  }
getBound() {
    const component = document.getElementById(this.id);
    if (!component) { 
      return {};
    }
    const rect = component.getBoundingClientRect();
    return {
      left: rect.left,
      top: rect.top + window.scrollY,
      width: rect.width || rect.right - rect.left,
      height: rect.height || rect.bottom - rect.top
    };
  }
 
  render() {
    return this.props.children;
  }
}

we receive props name from parent that will be a component id. getBoundingClientReact is a method that return an object containing height , width , top , left that we will use in the near future.

Step 3

Now, we will change this.props.children to be a function because it is easier to control and adjust the component that will be tracked.

// SizeObserver.js
...
render() {
  return this.props.children(this.id);
}
...

Test observer in index.js

// index.js
...
import styled from 'react-emotion';
const Box = styled("div")({
  width: 300,
  height: 150,
  background: "tomato",
  fontSize: "2em",
  fontWeight: "bold",
  color: "#fff",
  lineHeight: "150px"
});
function App() {
  return (
    <div className="App">
       <SizeObserver name="test">
         {id => <Box id={id}>Box</Box>}
       </SizeObserver
    </div>
  );
}
6jYRB3b.png!web

Step 4

update observer all the time.

// SizeObserver.js
...
componentDidMount() {
  this.intervalUpdate = setInterval(this.updatePosition, 50);
}
componentWillUnmount() {
  clearInterval(this.intervalUpdate);
}
updatePosition = () => {
  this.forceUpdate();
};
...
render() {
  return (
    const bound = this.getBound()
    console.log(bound)
    return this.props.children(this.id);
  )
}
...
2AB3Mry.png!web

Now you get the size and position of the component, it is time to display it.

Step 5

create Label component that will display width & height of the Box. You can create a new file, but for demonstration purpose I will add it inside SizeObserver.js .

// SizeObserver.js
const Label = styled('div')({
  position: 'fixed',
  background: 'rgba(0,0,0,0.5)',
  color: '#fff',
  padding: 8,
  zIndex: 100,
  '*': {
    margin: 0,
  }
})

Then use Portal to render the Label outside the Box to keep the Box clean. Refactor render method to this

// SizeObserver.js
...
render() {
  const bound = this.getBound()
  console.log(bound)
  return (
    <React.Fragment>
      {ReactDOM.createPortal(
        <Label
          style={{
            top: bound.top,
            left: bound.left,
          }}
        >
          <p>w: {bound.width}</p>
          <p>h: {bound.height}</p>
        </Label>,
        this.mountNode
      )}
      {this.props.children(this.id)}
    </React.Fragment>
  );
}
...
mUZjaev.png!web

Ta dah!. That’s right baby.

Testing

the most easiest way to test observer is to use css resize: both to the Box

resize: 'both',
overflow: 'auto',
// do not forget to add overflow: auto, otherwise won't work
bYjErar.png!web
resizable indicator at the bottom right corner
n2EN7jU.gif

Drag it to see changes. Tell me it works. Hola, Woo hoo.

Finally

If you want to track multiple components, the code will get ugly because you need to write SizeObserver with children as a function all the time. This is where HOC plays an important role here.

RVnuam3.png!web

create new file named withSizeObserver.js

I also add cuid() in SizeObserver.js because I want it to auto-gen id if no name from props provided.

This is the result.

vQneMzZ.png!web

That’s all of it. Thank you for reading. I will use SizeObserver for my next story:

Understanding Flex before using it. Stay tuned!

Feel free to comment below and ask anything! I’d be happy to help :+1:

Source code for SizeObserver , thank you Code Sandbox.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK