Is it possible to animate share buttons using Framer Motion but staggering siblings?

I have a Share Icon that looks like:

I'm looking to display the first 5 icons, with the 5th icon being the share icon. The remaining icons should appear below the share icon and expand to the right when someone taps or hovers over it (either tap or hover as I'm uncertain of the best UX approach in this scenario).

SocialShare.jsx

import React from 'react';
import { motion } from 'framer-motion';
import { ShareIcon } from '@heroicons/react/solid';

import {
  Facebook,
  HackerNews,
  Reddit,
  Twitter,
  LinkedIn,
  Pinterest,
  Telegram,
  Whatsapp,
  Pocket
} from '../icons/index';

const isProduction = process.env.NODE_ENV === 'production';

export const SocialShare = ({ title, slug }) => {
  const [share, openShare] = React.useState(false);
  const [host, setHost] = React.useState('');

  React.useEffect(() => {
    setHost(window.location.host);
  }, []);

  const url = `${isProduction ? 'https://' : 'http://'}${host}/${slug}`;
  const text = title + url;
  const via = 'deadcoder0904';
  const sharer = {
    facebook: `https://www.facebook.com/sharer/sharer.php?u=${url}`,
    twitter: `https://twitter.com/intent/tweet?url=${url}&text=${text}&via=${via}`,
    reddit: `https://www.reddit.com/submit?title=${title}&url=${url}`,
    hackernews: `https://news.ycombinator.com/submitlink?u=${url}&t=${title}`,
    linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${url}`,
    pinterest: `https://pinterest.com/pin/create/button/?url=${url}&description=${title}`,
    telegram: `https://telegram.me/share/url?url=${url}&text=${text}`,
    whatsapp: `https://wa.me/?text=${title}%0D%0A${url}%0D%0A%0D%0A${text}`,
    pocket: `https://getpocket.com/edit.php?url=${url}`
  };

  const variants = {
    hidden: {
      opacity: 0,
      translateX: -16
    },
    visible: {
      opacity: 1,
      translateX: 0
    }
  };

  return (
    <ul className="flex items-center mt-8 space-x-4">
      <li>
        <a className="" href={sharer.facebook} title="Share on Facebook">
          <Facebook />
        </a>
      </li>
      <li>
        <a className="" href={sharer.twitter} title="Share on Twitter">
          <Twitter />
        </a>
      </li>
      <li>
        <a className="" href={sharer.reddit} title="Share on Reddit">
          <Reddit />
        </a>
      </li>
      <li>
        <a className="" href={sharer.hackernews} title="Share on Hacker News">
          <HackerNews />
        </a>
      </li>
      <motion.li className="cursor-pointer" whileHover={{}}>
        <ShareIcon
          className="w-6 h-6 text-gray-300"
          onClick={() => {
            openShare(!share);
          }}
        />
      </motion.li>

      <motion.li
        className=""
        initial="hidden"
        animate="visible"
        variants={variants}
        transition={{
          type: 'tween',
          ease: 'easeInOut'
        }}
      >
        <a className="" href={sharer.linkedin} title="Share on LinkedIn">
          <LinkedIn />
        </a>
      </motion.li>
      <li>
        <a className="" href={sharer.pinterest} title="Share on Pinterest">
          <Pinterest />
        </a>
      </li>
      <li>
        <a className="" href={sharer.telegram} title="Share on Telegram">
          <Telegram />
        </a>
      </li>
      <li>
        <a className="" href={sharer.whatsapp} title="Share on Whatsapp">
          <Whatsapp />
        </a>
      </li>
      <li>
        <a className="" href={sharer.pocket} title="Share on Pocket">
          <Pocket />
        </a>
      </li>
    </ul>
  );
};

The icons are within a flex container which makes using staggerChildren challenging. Although Stagger Children would be ideal for my requirements, Flexbox does not seem to offer a solution for this issue.

Should I consider altering the DOM elements by introducing a wrapper? However, this may disrupt the ul>li structure.

All I aim for is to have all the icons expand to the right when hovering over the share icon, and retract back underneath it upon removing the cursor. Essentially, replicating the functionality seen in https://codepen.io/romswellparian/full/mJXdqV:

The behavior should exclusively include expanding to the right, with the first four icons always visible.

A complete reproduction can be found on Stackblitz → https://stackblitz.com/edit/share-animation-framer-motion?file=components%2FSocialShare.jsx

Answer №1

Children Animation using Flexbox

Don't let Flexbox hinder your use of the staggerChildren feature.

To implement staggerChildren, include it in the transition property of the parent variants. This means that both the parent element containing the list items and the list items themselves should be motion components to enable animation.

const listVariants = {
  hidden: {
    transition: {
      staggerChildren: 0.1,
      staggerDirection: -1
    }
  },
  visible: {
    transition: {
      staggerChildren: 0.1
    }
  }
};

const itemVariants = {
  hidden: {
    opacity: 0,
    x: -16
  },
  visible: {
    opacity: 1,
    x: 0
  }
};

return (
    <motion.ul
      variants={listVariants}
      initial="hidden"
      animate={share ? 'visible' : 'hidden'}
    >
      <motion.li variants={itemVariants}>
        <a href={sharer.linkedin} title="Share on LinkedIn">
          <LinkedIn />
        </a>
      </motion.li>
      {/* ...etc */}
    </motion.ul>
);

A demo showcasing what you've described can be found here:
https://stackblitz.com/edit/share-animation-framer-motion-e5fp5p?file=components/SocialShare.jsx

Enhance Animation with AnimatePresence

If you wish to completely remove hidden items from the DOM, consider utilizing AnimatePresence. This involves encapsulating entering and exiting elements within the <AnimatePresence> tag, each requiring unique keys and defined animation variants for initial, animate, and exit.

Staggering animations with
Dynamic Variants along custom props allow for delayed enter and exit animations.

Example:

const itemVariants = {
  hidden: i => ({
    opacity: 0,
    x: -16,
    transition: {
      delay: i * 0.1
    }
  }),
  visible: i => ({
    opacity: 1,
    x: 0,
    transition: {
      delay: i * 0.1 // custom prop used to stagger delay
    }
  })
};

return (
  <ul className="flex items-center mt-8 space-x-4">
    <AnimatePresence>
      {share && (<>
        <motion.li
          variants={itemVariants}
          key="linkedin"    /* don't forget key! */
          initial="hidden"
          animate="visible"
          exit="hidden"
          custom={1}  /* custom prop used to stagger delay */
        >
          <a href={sharer.linkedin} title="Share on LinkedIn">
            <LinkedIn />
          </a>
        </motion.li>

        <motion.li
          variants={itemVariants}
          key="pinterest"
          initial="hidden"
          animate="visible"
          exit="hidden"
          custom={2}
        >
          <a href={sharer.pinterest} title="Share on Pinterest">
            <Pinterest />
          </a>
        </motion.li>
        {/* etc... */}
      </>)}
    </AnimatePresence>
  </ul>
)

View the implementation with AnimatePresence here:
https://stackblitz.com/edit/share-animation-framer-motion-wjdxhc?file=components/SocialShare.jsx

The resizing issue when removing elements from the container is addressed by setting an explicit width or adjusting flex alignment based on project requirements.

Answer №2

In continuation of @Cadin's response, I have discovered that combining the use of staggerChildren and AnimatePresence can result in a more seamless animation effect when displaying lists.

const staggerVariants = {
  animate: {
    transition: {
      staggerChildren: 0.5,
    },
  }
};

const itemVariant = {
 hidden: { height: 0 },
 visible: { height: 'auto' }
};

<motion.div variants={staggerVariants} initial="animate" animate="animate">
  <AnimatePresence>
    {array.map((item) => (
      <motion.div
        key={item.id}
        variants={itemVariant}
        initial="hidden"
        animate="visible"
        exit="hidden"
      >
        ...
      </motion.div>
    ))}
  </AnimatePresence>
</motion.div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Sporadic success with Ajax operations

The functionality of this ajax code seems to be intermittent. Can someone help me troubleshoot the issue? App.controller('sendemail', function (page) { $.ajax({ type: 'GET', url: 'http://sanjuwebworks.com/conte ...

Adjust the height of the box based on the content within its flexbox

I have a flexbox div nested inside another div, and I'm trying to adjust the height of the outer box based on the content of the inner flexbox. Here is the link to the current fiddle. The issue I am facing is that the text in the second box overflows ...

Tips for preventing the appearance of two horizontal scroll bars on Firefox

Greetings, I am having an issue with double horizontal scroll bars appearing in Firefox but not in Internet Explorer. When the content exceeds a certain limit, these scroll bars show up. Can someone please advise me on how to resolve this problem? Is ther ...

The Element component functions perfectly in Tailwind Play but is experiencing issues when implemented on the live webpage

I'm in the process of developing reusable components for a button that can include both an icon and a label. For this project, I'm utilizing React+Next along with Tailwind. The default state for the button is displaying correctly, but I am encoun ...

Enhance the header image by incorporating background "bars" that add color to the overall design. Alternatively, overlay an image on top of div

I designed a header image for my website, but I'm struggling to make it extend the full width of the user's web browser with black "bars" on the sides. I've attempted a few solutions, but haven't been successful so far. Currently, my c ...

Angular : How can a single item be transferred from an array list to another service using Angular services?

How to Transfer a Single List Item to the Cart? I'm working on an Angular web application and I need help with transferring a single item from one service to another service and also displaying it in a different component. While I have successfully i ...

Enhance AppSync data caching

I recently launched a website on AWS Amplify Hosting with the front-end built using Next.js. The backend is powered by AWS App Sync and DynamoDB, utilizing the API Category in Amplify. Interestingly, I have set caching to None in the AppSync API. One of t ...

Having trouble invoking an established route within a different route in an Express JS project

While working with an Express JS application connected to a mySQL database, I encountered an issue when trying to fetch data from a pre-defined route/query: // customers.model.js CUSTOMERS.getAll = (result) => { let query = "SELECT * FROM custo ...

Android experiencing issues with dynamically loading Border XML

I am having trouble setting a border dynamically for a RelativeLayout. Oddly enough, when I manually add the border in the activity XML file, it displays perfectly. However, when I try to generate the border dynamically, it doesn't appear. border.xml ...

Can you employ a right-side offset in Bootstrap 4?

Is it possible to align a button with a textbox using the Bootstrap 4 layout system? https://i.sstatic.net/tN9II.png <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"/> <div ...

What could be the reason behind the malfunction of this jQuery post request?

I am currently studying AJAX with jQuery, but I am facing an issue with my registration system. The following code does not seem to work: $('#submitr').click(function () { var username = $('#usernamefieldr').val(); var password ...

Utilizing Google APIs to split a route among multiple locations

I am facing a scenario where A laundry company operates from one shop location. The laundry company has 3 trucks available (n trucks). The laundry company needs to deliver washed clothes to multiple locations (n locations). https://i.sstatic.net/ULup8.pn ...

Exporting modules in node.js is driving me crazy. Just four lines of code and I can't figure out what's wrong

Currently, I am delving into the realm of node.js through an online course on Udemy. However, I've run into a perplexing issue that seems to defy logic and reason. The lesson revolves around accessing external files in node.js using module.exports. I ...

Glowing sphere in Three.js

Can someone help me with a coding dilemma I'm facing? I'm trying to create a sphere that functions as a source of light, like the sun. I've been experimenting with the meshPhongMaterial options emissive: color and shininess: intensity, but s ...

What is the best way to adjust the height of a side-panel to match the dynamic

I'm facing an issue where my side-panel height is not expanding along with the content of the div. While specifying a height of 100% works well for mobile view, I encounter problems in desktop view as the sidebar's height remains fixed. I want th ...

The delete function is not functioning

I need help with implementing a custom Angular directive that includes a delete button to remove itself. When I click the button removeMe, it is not deleting an item from the array. Any suggestions on what might be causing this issue? HTML: <button t ...

How can I organize an array in JavaScript by date for presentation on a webpage?

Check out this code snippet: list.component.ts const data1 = [ { dateStart: "2020-02-14 00:00:01", name: 'Server1' }, { dateStart: "2020-02-13 14:00:01", name: 'Server1' }, ...

Error: HTMLAnchorElement.linkAction is attempting to access an undefined property 'add', resulting in an uncaught TypeError

Displaying the following: Error: Cannot read property 'add' of undefined at HTMLAnchorElement.linkAction const navigationLinks = document.querySelectorAll('.nav__link') function handleLinkAction(){ // Activate link navLin ...

Troubleshooting problem with Angular4's HTTP requests

While working on my Angular 4 application, I am creating an oath guard service to check the validity of tokens. If the token is not valid, I want the user to log in again. Below are the functions I have implemented for this purpose: isLogedIn(){ ret ...

What is the significance of using index 0 for caching an ID in jquery?

What is the reason behind using an index of 0 in the following code? var $one = $('#one')[0]; Is there a specific purpose for not just using var $one = $('#one'); ? SOURCE I came across the above code while researching about jQuery, ...