Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SVG scale animation slow on iOS

I'm trying to add a scale animation to an SVG. Its working fine in Webkit based browsers, but on iOS Chrome and Safari the animation is super slow. Here is the page I am trying to animate. Here is the relevant code...

const HomeHeading = styled.svg`
  margin: 0;
  color: rgba(255, 255, 255, 1);
  height: 100vh;
  width: 100vw;
  position: fixed;
  top: 0;
  left: 0;
  backface-visibility: hidden;
  perspective: 1000;
  transform: scale(
      ${props =>
        props.scrollPosition / props.scale < 1
          ? 1
          : props.scrollPosition / props.scale}
    )
    translateZ(0);
  transform-origin: 42% 56%;

  @media screen and (max-width: 480px) {
    transform-origin: 43% 38% !important;
  }

  rect {
    -webkit-mask: url(#mask);
    mask: url(#mask);
    fill: #f00;
  }

  defs {
    mask {
      rect {
        fill: white;
      }
      text {
        transform: translateY(10%);
        font-size: 8vw;

        @media screen and (max-width: 480px) {
          transform: translateY(0);
        }

        &:last-child {
          @media screen and (max-width: 480px) {
            transform: translateY(0);
          }
          transform: translateY(20%);
        }
      }
    }
  }
`;
const HomeSubHomeSectionHeading = styled.section`
  width: 100vw;
  height: 100vh;
  top: 0;
  left: 0;
  background: #1ecbe1;
  position: fixed !important;
  display: flex;
  align-content: center;
  justify-content: center;
  flex-wrap: wrap;
`;

const ColorChanger = styled.div`
  position: fixed;
  width: 100vw;
  height: 100vh;
  top: 0;
  left: 0;
  z-index: 3;
  background-color: rgb(255, 255, 255);
  opacity: ${props => 3000 / props.scrollPosition / 20};
`;

const ImageStamp = styled.div`
  width: 280px;
  height: auto;
  display: inline-block;
  padding: 10px;
  background: white;
  position: relative;
  -webkit-filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
  background: radial-gradient(
    transparent 0px,
    transparent 4px,
    white 4px,
    white
  );
  background-size: 20px 20px;
  background-position: -10px -10px;

  &:after {
    content: "";
    position: absolute;
    left: 5px;
    top: 5px;
    right: 5px;
    bottom: 5px;
    z-index: -1;
  }

  @media screen and (max-width: 768px) {
    width: 200px;
    margin-bottom: 30px;
  }
`;

const MeSection = styled.section`
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
`;

const MePhoto = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1 0 33.333%;
`;

const MeBio = styled.div`
  flex: 1 0 66.666%;
  color: #ffffff;
  padding: 0 30px;
`;

const MeSocials = styled.div`
  svg {
    margin: 0 10px;
  }
`;

const SocialLink = styled.a`
  color: #fff;
  &:hover {
    color: #f1f1f1;
  }
`;

const IndexPage = props => {
  const [scrollPosition, setScrollPosition] = useState(0);
  const [scale, setScale] = useState(0);

  useEffect(() => {
    window.addEventListener("scroll", handleScroll, { passive: true });
  });

  const handleScroll = () => {
    const position = window.scrollY;
    setScale(document && document.width > 500 ? 20 : 5);
    setScrollPosition(position);
  };

  return (
    <Layout>
      <HomeSubHomeSectionHeading>
        <ColorChanger scrollPosition={scrollPosition}></ColorChanger>
        <MeSection>
          <div className="container">
            <MePhoto>
              <ImageStamp>
                <Img fluid={props.data.mattImage.childImageSharp.fluid} />
              </ImageStamp>
            </MePhoto>
            <MeBio>
              <h3>Hi, I'm Matt!</h3>
              <hr />
              <p>
                I'm a Lead Frontend Developer currently based at Oliver Wyman
                Digital. I have experience in a range of frontend technologies
                and practices; more recently dabbling with AB testing, VueCLI
                and Typescript.
              </p>
              <p>
                Outside of the web world, I like to run, travel and like to
                watch movies. Apart from Toy Story 1, I cried when I found out
                Buzz Lightyear couldn't fly.
              </p>
              <h4>Find out more</h4>
              <hr />
              <MeSocials>
                <SocialLink
                  href="https://uk.linkedin.com/in/mattmaclennan"
                  target="_blank"
                >
                  <FontAwesomeIcon icon={faLinkedin} />
                </SocialLink>
                <SocialLink href="https://github.com/mmaclenn" target="_blank">
                  <FontAwesomeIcon icon={faGithub} />
                </SocialLink>
              </MeSocials>
            </MeBio>
          </div>
        </MeSection>
      </HomeSubHomeSectionHeading>
      <HomeHeading
        preserveAspectRatio="xMinYMin meet"
        scale={scale}
        scrollPosition={scrollPosition}
      >
        <defs>
          <mask id="mask" x="0" y="0" width="100%" height="100%">
            <rect x="0" y="0" width="100%" height="100%" fill="#fff"></rect>
            <text x="50%" y="40%" textAnchor="middle">
              Matt Maclennan
            </text>
            <text id="editText" x="50%" y="45%" textAnchor="middle">
              Web Developer
            </text>
          </mask>
        </defs>
        <rect
          x="0"
          y="0"
          width="100%"
          height="100%"
          fill="#E1341E"
          id="mask"
        ></rect>
      </HomeHeading>
    </Layout>
  );
};

export const fluidImage = graphql`
  fragment fluidImage on File {
    childImageSharp {
      fluid(maxWidth: 1000) {
        ...GatsbyImageSharpFluid
      }
    }
  }
`;

export const pageQuery = graphql`
  query {
    mattImage: file(relativePath: { eq: "me.jpg" }) {
      ...fluidImage
    }
  }
`;

export default IndexPage;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

I'm aware I can only animate transforms and opacity, also I've added translateZ to use hardware rendering and backface-visibility to the CSS with no luck.

Any ideas where I'm going wrong?

Edit: As per the comments, I have tried to throttle the scroll callback by using this package. Here is the code I'm using based on this package...

useScrollPosition(
    ({ prevPos, currPos }) => {
      setScale(document && document.width > 500 ? 20 : 5);

      const shouldBeStyle = {
        transform: `scale(${
          Math.abs(currPos.y) < 9 ? 1 : Math.abs(currPos.y) / scale
        }) translateZ(0)`,
        pointerEvents: `${Math.abs(currPos.y) > 1000 ? "none" : "auto"}`,
      };

      const opacityStyle = {
        opacity: 1000 / Math.abs(currPos.y) / 20,
      };

      if (JSON.stringify(shouldBeStyle) === JSON.stringify(scrollStyling))
        return;

      setOpactiyStyling(opacityStyle);

      setScrollStyling(shouldBeStyle);
    },
    [scrollStyling, opacityStyling]
  );
like image 208
Matt Maclennan Avatar asked Nov 06 '22 07:11

Matt Maclennan


1 Answers

  useEffect(() => {
   window.addEventListener("scroll", handleScroll, { passive: true });
  });

This adds an event listener every time props change. You probably only want to do this once, e.g.:

  useEffect(() => {
   window.addEventListener("scroll", handleScroll, { passive: true });
  }, []);

or at the very least put some dependencies in those brackets. See: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

like image 194
JKettler Avatar answered Nov 15 '22 06:11

JKettler