GithubHelp home page GithubHelp logo

aweary / react-perimeter Goto Github PK

View Code? Open in Web Editor NEW
1.0K 9.0 27.0 156 KB

๐Ÿšง Create an invisible perimeter around an element and respond when its breached.

License: MIT License

JavaScript 100.00%
react perimeter mouse-events

react-perimeter's Introduction

react-perimeter ๐Ÿšง

Create an invisible padding around an element and respond when its breached.

Usage Example

react-perimeter exports a single Perimeter component that will register a mousemove listener and calculate whether the current mouse position is within a padding.

The padding will be calculated using getBoundingClientRect and the padding prop, which lets you define "padding" for the perimeter.

<Perimeter
  onBreach={this.prefetch}
  padding={60}>
  <button onClick={this.fetch}>Load More</button>
</Perimeter>

Perimeter by default will wrap its children in a span and use that to calculate the boundries. If you want to avoid the wrapping span, or you want the padding to be calculated from another element, you can use a render callback.

<Perimeter
  onBreach={this.prefetch}
  padding={60}>
  {ref => (
    <button
      ref={ref}
      onClick={this.fetch}>Load More</button>
  )}
</Perimeter>

The render callback is passed a ref callback which should be passed to the ref prop of the element you want to use.

Installation

yarn add react-perimeter

API

Props

Property Type Default Description
padding number 0 The buffer around the element that defines the padding of the perimeter
onBreach () => void undefined A callback to be invoked when the perimeter is breached
once boolean false Whether the callback should only be invoked once (for example, when prefetching some data or chunks). If true all event listeners will be removed after onBreach is called.
mapListeners EventListener => EventListener undefined If provided, each event listeners (resize, mousemove) will be passed in, and the returned function will be used instead.

Debouncing or Throttling

You may want to debounce or throttle the mousemove and resize event listeners if you've profiled your application and determined that they are noticeably affecting your performance. You can do so using the mapListeners prop, which takes a function that should accept an event listener and return a new function to be used instead.

<Perimeter mapListeners={listener => debounce(listener, 20)}>

By letting you provide the mapped listener yourself, react-perimeter gives you full control over what debounce/throttle imeplementation you wish to use and its paramaters.

Deduping Event Listeners

If you use react-perimeter in multiple places in your application you may want to dedupe the internal event listeners.

react-perimiter integrates with react-listener-provider to make deduping easy. Simply yarn add react-listener-provider and wrap your application like this:

import ReactListenerProvider from 'react-listener-provider';
<ReactListenerProvider>
   <YourApp>
       <Perimeter />
   </YourApp>
</ReactListenerProvider>

Any <Perimeter> component you use inside of <ReactListenerProvider> will automatically use the global event listener provided by react-listener-provider instead of registering its own.

Prefetching or Preloading

react-perimeter shines especially bright when used to prefetch or preload other components. Here is a small example that uses react-loadable and react-router to preload a route chunk when the cursor gets near a link:

import React from 'react'
// Assume this is the component returned from `react-loadable`, not the page itself
import OtherPage from './routes/other-page'
import Perimeter from 'react-perimeter'
import { Link } from 'react-router'

const App = () => (
  <div>
    <h1>Home Page!</h1>
    <p>Here's some content</p>
    <Perimeter padding={100} onBreach={OtherPage.preload} once={true} >
      <Link to="other">Other Page</Link>
    </Perimeter>
  </div>
)

react-loadable provides an extremely useful static preload method that begins fetching the chunk for us. We pass this to onBreach so that the preloading begins as soon as the mouse is within 100 pixels of the Link component. We also pass in the once prop to tell react-perimeter that we only want to respond to the first breach. This means that, after the preload request has been issued, the listeners will be deregistered, removing any unneeded overhead.

We can go one step further and abstract this out into its own component, PreloadLink:

const PreloadLink = ({ to, children, preload }) => (
  <Perimeter padding={100} onBreach={preload.preload} once={true}>
    <Link to={to}>{children}</Link>
  </Perimeter>
)
<PreloadLink to="about" preload={AboutPage} />

react-perimeter's People

Contributors

aweary avatar binoy14 avatar codincat avatar istarkov avatar jnsdls avatar stephenmathieson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-perimeter's Issues

Moving content breaks perimeter

Hi,

First of all, thanks for the lib. It's simple but smart!

However, one of my concerns is that when the content is moving, it actually breaks the perimeter since this.bounds is not updated. (I've checked the ongoing PRs and they dont seem to address this problem)
The resize window event is a first step but is not enough.

I originally thought about adding the componentDidUpdate lifecycle method but if the updated content is not in the component itself it won't solve the issue (see updated demo in my fork https://github.com/JulienPradet/react-perimeter).

One way, though, to address this problem would be to update bounding shapes using a requestIdleCallback (in order to avoid freezing important stuff going on) and debouncing it (since the mouse's movements fire a lot of events) directly. This would be doable in the provider that will land in @jnsdls 's PR. This solution allows people not to think about updating their shapes and keeps the API simple. However it might lead to performance issues since there would be ton of updates.

If people want a more manual way to update those, the automatic update could be disabled and the update method could be exposed in the context in order for them to call it in their own component lifecycles.

If what I'm talking about is not in the scope of the lib, feel free to ignore it.
Additionnally, if what I'm saying is not clear enough I could land a POC if you're interested in it.

๐Ÿ‘‹

Edit: Oh, and thinking about it, the perimeter could check the hover event on the registered DOM node too! This way, even if the perimeter is broken, the preload would still launches on hover which is still nice to have. ๐Ÿ™‚

Request #20 publish to npm

Hi,

Related to #20

I see that the PropTypes have been upgraded to [prop-types]
https://github.com/aweary/react-perimeter/blob/master/src/index.js#L48-L60

Though it looks like 0.3.1 published to NPM still has the references to React.PropTypes
https://unpkg.com/[email protected]/lib/index.js

Perimeter.propTypes = {
  onBreach: _react2.default.PropTypes.func.isRequired,
  once: _react2.default.PropTypes.bool,
  padding: _react2.default.PropTypes.number.isRequired,
  mapListeners: _react2.default.PropTypes.func
};
Perimeter.contextTypes = {
  listener: _react2.default.PropTypes.shape({
    add: _react2.default.PropTypes.func.isRequired,
    remove: _react2.default.PropTypes.func.isRequired
  })
};

Cheers

accessibility, focus event listener

What do you think about adding focus event listener for non-mouse users? I'm undecided myself if this is a good idea but I was thinking of ways to make this accessible. The problem with a focus listener is that a user tabbing through items on the page would trigger everything as they navigate with the keyboard.

Thoughts? Concerns?

Great idea by the way!

Only works if I resize the window.

onBreach doesn't fire unless I resize the window.
Using it like this;

<Perimeter
onBreach={() => console.log('should write to console')}
padding={24}
once={true}>
  <Link to="/link">Link</Link>
</Perimeter>

Add a live demo

Hi,
It will be great to have a live demo site so that people can see it in action.
Maybe we can create a GitHub page for it.

Add test suite

It's probably hard to test the actual core feature of react-perimeter , but there are a few things we can definitely test:

  • correctly exports the React component
  • renders children
  • attaches ref correctly with render callbacks
  • registers event listeners on mount
  • removes event listeners on unmount
  • removes event listeners after onBreach is called if once is true
  • more stuff

I'd like to use jest and enzyme for this.

Dedupe event listeners

Currently, each instead of Perimeter will register a mousemove and resize listener on window. While I don't recommend having tons of these on a single page, it may be better for performance to register a single listener and iterate over each registered instance. So we need to:

  • Determine if its more performant to use a single listener (maybe browsers can parallelize multiple listeners better?)
  • Find a universal way for each instance to register with a global listener. The easiest solution may be to track a property on the global window but other suggestions welcome

Track horizontal scroll offset

We already track the vertical offset (pageYOffset) so the perimeter is calculated correctly on scroll (and if the component mounts on a page that is already scrolled).

Horizontal scroll is less common, but still it's good idea to support it nonetheless.

Feature: precognition

Just watched the React Europe talk. Sounds like a cool component. But I was thinking what you really want to know is will the mouse enter the component not is it close. You could do this by interpolating the mouse trajectory based on mouse position, speed and acceleration.

// each interval
x = mouse.x
xspeed = x - prevx
xacc = xspeed - prevxspeed

//calculate mouse pos 10 frames ahead
for (let i=0; i < 10; i++ ) {
  xspeed = xspeed + xacc
  x = x + xspeed
  // if (x, y) in box and (xspeed**2 + yspeed**2) < 10
  // mouse very likely to rest in box within 10 frames
}

I used this logic in a game where you controlled firefly. A fish would jump out of the water only if based on pre calculating both trajectories it would eat you in ten frames (providing you did not take evasive action). The fish seldom missed and always took you by surprise if you flew too close to the water. What felt cool was the firefly's speed made no difference to the fishes accuracy.

the fish is only missing here because he is photoshopped in

10 frames would give you 160ms to preload even if the mouse was moving fast towards the button from a long distance.

Provide API for determining perimeter shape

Currently, the perimeter is just a square extending outwards from the element in all directions. It would be awesome if we could provide an API that lets you determine any arbitrary shape or conditions for the perimeter breach.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.