Emotional and Functional UI Animations in React

December 02, 2022 2 min read

Animation can make a huge difference in how users perceive a product. From visual feedback to status changes and even just a touch of fun. Animations give you a world of options!

As part of my talk on December 2 2022 at React day Berlin, I showed the following demos to illustrate how functional and emotional UI animations make a React app more intuitive and memorable.

A simple hover button animation

This is a functional micro-animation that is triggered by a user interaction. When a user hovers a button, the button should animate to indicate that the button is clickable.

import * as React from "react";
import { Box, Button } from "@chakra-ui/react";
import { css } from "@emotion/react";

export default function Example() {
  return (
    <Box>
      <Button
        colorScheme='purple'
        size='lg'
        css={css`
          // transition: <property> <duration> <timing-function> <delay>
          transition: transform 250ms ease-in-out 0s, 
            background-color 250ms ease-in-out 0s;
        
          &:hover {
            transform: translateY(-1px);
          }
      `}>
        Button
      </Button>
    </Box>
  );
}

A button that shows a line on click

This is a functional animation that is triggered by a user interaction. When a user hovers a button, the button should animate to indicate that the button is clickable.

import { useState } from "react";
import { Flex, Text } from "@chakra-ui/react";
import ButtonLine from "./ButtonLine";

export default function BellRinging() {
  const [activeItem, setActiveItem] = useState("about");

  return (
    <Flex
      direction="row"
      background="#111b27"
      justifyContent="space-between"
      alignItems="center"
      padding="1em"
      color="gray.100"
    >
      <Text fontSize={["2xl", "2xl", "md"]} fontWeight="semibold">
        <svg
          viewBox="0 0 100 100"
          xmlns="http://www.w3.org/2000/svg"
          width="50"
          height="50"
          fill="#d6d8e7"
        >
          <circle cx="50" cy="50" r="50" />
        </svg>
      </Text>
      <Flex alignItems="center" flexDirection="row">
        <ButtonLine
          isActive={activeItem === "about"}
          onClick={() => setActiveItem("about")}
        >
          About
        </ButtonLine>

        <ButtonLine
          isActive={activeItem === "speaking"}
          onClick={() => setActiveItem("speaking")}
        >
          Speaking
        </ButtonLine>

        <ButtonLine
          isActive={activeItem === "blog"}
          onClick={() => setActiveItem("blog")}
        >
          Blog
        </ButtonLine>
      </Flex>
    </Flex>
  );
}

A bell ringing in a header

This is a functional animation that should be triggered by a state change in the application. It is used to provide feedback to the user about the state of the application. For example, when a user receives a notification, the bell should animate to indicate that the user has a new notification.

For this example we're checking if users have the reduce motion setting enabled in their operating system. If they do, we're not animating the bell.

import { useState } from "react";
import { BellIcon } from "@chakra-ui/icons";
import { Flex, Box, Button } from "@chakra-ui/react";
import { motion, useReducedMotion } from "framer-motion";

export default function BellRinging() {
  const [isRinging, setIsRinging] = useState(false);
  // A hook that returns true if the current device has Reduced Motion setting enabled.

  const shouldReduceMotion = useReducedMotion();
  const rotations = shouldReduceMotion
    ? 0
    : [
        0,
        30,
        -28,
        0,
        34,
        -32,
        30,
        -28,
        26,
        -24,
        22,
        -20,
        18,
        -16,
        14,
        -12,
        10,
        -8,
        6,
        -4,
        2,
        -1,
        0
      ];

  const rotationVariants = {
    inactive: {
      rotate: 0
    },
    active: {
      rotate: rotations
    }
  };

  return (
    <>
      <Flex
        direction="row"
        background="#111b27"
        justifyContent="space-between"
        alignItems="center"
        padding="1em"
        color="gray.100"
      >
        <span>Logo</span>
        <motion.span
          initial="inactive"
          animate={isRinging ? "active" : "inactive"}
          variants={rotationVariants}
          transition={{ duration: 2 }}
          onAnimationComplete={() => {
            setIsRinging(false);
          }}
        >
          <BellIcon width={6} height={6} color="gray.200" />
        </motion.span>
      </Flex>
      <Box paddingTop="4">
        <Button size="xs" onClick={() => setIsRinging(true)}>
          Animate
        </Button>
      </Box>
    </>
  );
}

A like button that animates on click

This is an emotional animation. It could be a simple like button but we're using an cute heart that animates on click.

import { useState } from "react";
import LoveButton from "./LoveButton";
import { Button, Flex, Box } from "@chakra-ui/react";

export default function Demo() {
  const [userLikes, setUserLikes] = useState(0);
  const [totalLikes, setTotalLikes] = useState(112);

  const onClickButton = () => {
    setUserLikes(userLikes + 1);
    setTotalLikes(totalLikes + 1);
  };

  const onReset = () => {
    setUserLikes(0);
  };

  return (
    <Flex direction="column" justifyContent="center" alignItems="center">
      <LoveButton
        onClickButton={onClickButton}
        userLikes={userLikes}
        totalLikes={totalLikes}
      />
      <Box paddingTop="4">
        <Button size="xs" onClick={onReset}>
          Reset user likes
        </Button>
      </Box>
    </Flex>
  );
}

A search bar with a planet that animates

This is an emotional animation. It was originally used on isthereuber.in.

import { useState } from "react";
import { Input, VStack, HStack, IconButton } from "@chakra-ui/react";
import { SearchIcon } from "@chakra-ui/icons";
import Planet from "./Planet";

export default function Demo() {
  const [value, setValue] = useState("");
  const [isSearching, setSearching] = useState(false);
  const [isSearchComplete, setIsSearchComplete] = useState(false);

  const handleChange = (e: any) => {
    setValue(e.target.value);
    if (e.target.value) {
      setSearching(true);
    } else {
      setSearching(false);
    }
  };

  const onClick = () => {
    setIsSearchComplete(true);
  };

  const onAnimationComplete = () => {
    setIsSearchComplete(false);
  };

  return (
    <VStack>
      <Planet
        isSearching={isSearching}
        isSearchComplete={isSearchComplete}
        onAnimationComplete={onAnimationComplete}
      />
      <HStack>
        <Input
          type="search"
          name="search"
          value={value}
          onChange={handleChange}
        />
        <IconButton
          aria-label="Search"
          icon={<SearchIcon />}
          onClick={onClick}
        />
      </HStack>
    </VStack>
  );
}

Share on TwitterEdit on GitHub

0

© 2022-present Elizabet Oliveira