ReactMotion - Mouse dependent StaggeredMotion

Follow first, last or middle item. Also normalize the mouse position to get reasonable values to use for scaling of letters.

by jonahe

HTML

<script src="https://unpkg.com/react-motion/build/react-motion.js"></script>
<div id="app"></div>

CSS

body {
  background: teal;
  padding: 10px;
  height: 100vh;
  box-sizing: border-box;
  font-family: Helvetica;
}

#app {
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 4px;
  padding: 10px;
  height: 100%;
  box-sizing: border-box;
}
.animation-container {
  display: flex;
}
.char-element {
  font-size: 40px;
  white-space: pre-wrap;
  user-select: none;
  transition: color 1s;
}

React

const {StaggeredMotion, spring, presets} = ReactMotion;

const SPRING_INITIAL_OFFSET_X = 50;
const SPRING_INITIAL_OFFSET_Y = -70;
const SPRING_DESTINATION_X = 0;
const SPRING_DESTINATION_Y = 0;

// SO copy paste
function normalize(min, max) {
    var delta = max - min;
    return function (val) {
        return (val - min) / delta;
    };
}

// let all other values move toward the first, last or middle value
const FOLLOW_MODES = {
	FIRST : "FIRST",
  LAST : "LAST",
  MIDDLE : "MIDDLE"
};

const FOLLOW_MODE = FOLLOW_MODES.MIDDLE;

const CONTENT_TO_ANIMATE = "Try moving the mouse around";
const SPLITTING_MODES = {
	ON_WORD : /\b/, // keeps white-space, so "hello world" -> ["hello", " ", "world"]
  ON_EVERY_CHAR : "",
  NO_SPLIT : "[\s\S]*", // regex that matches everything
};
const SPLITTING_FUNCTION = content => content.split(SPLITTING_MODES.ON_EVERY_CHAR);

const paddingToSubtract = 40;
const normalizedMouseYpos = normalize(1, document.getElementById('app').getBoundingClientRect().height - paddingToSubtract);

const getInitialStylesFn = (itemsToAnimate) => (mouseX, mouseY) => {
	return itemsToAnimate.map(() =>  ({
  	x: mouseX,
    y: mouseY,
 })
)};

// available gentle, wobbly, stiff
// /*
const myPreset = Object.assign({}, presets.stiff, {precision: 1})
const mySpring = springTo => spring(springTo,myPreset);

/* use this if you want to follow first or last item */
const prevToNextStylesTransform = (followLast) => (mouseX, mouseY) => (prevStyles) => {
	const nextStyles = prevStyles.map((prevStyle, i) => {
  	
  	if(followLast ? (i == prevStyles.length - 1) : i == 0) {
    	return { 
      	x: mySpring(mouseX < 30 ? 0 : mouseX - 30),
        y: mySpring(mouseY < 50 ? 0 : mouseY - 50),
      };
    } else {
    	const nearestSibling = prevStyles[i + (followLast ? 1 : -1)];
    	const {x: siblingX, y: siblingY} = nearestSibling;
    	return { x: mySpring(siblingX), y: mySpring(siblingY) };
    }
  });
  return nextStyles;
};

/*  use this to follow the middle one...