<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Noël's Blog]]></title><description><![CDATA[I am a full-stack developer passionate about building human-centric user interfaces for the web. I specialise in Next.js, React, and JavaScript. Take a look at ]]></description><link>https://blog.noelcserepy.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 03:10:01 GMT</lastBuildDate><atom:link href="https://blog.noelcserepy.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Designing User Interfaces For The AI Era]]></title><description><![CDATA[Introduction
A lot is happening in UI. And I mean A LOT.
Text generation, image generation, audio & speech generation and even app generation. It feels like we are finally living in the future. We have unlocked immense capability, productivity and ev...]]></description><link>https://blog.noelcserepy.com/designing-user-interfaces-for-the-ai-era</link><guid isPermaLink="true">https://blog.noelcserepy.com/designing-user-interfaces-for-the-ai-era</guid><category><![CDATA[AI]]></category><category><![CDATA[UI]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Noël Cserépy]]></dc:creator><pubDate>Thu, 28 Sep 2023 10:14:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695895225424/a5cd3de2-bc90-43ab-8949-3af4b44a9e11.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>A lot is happening in UI. And I mean A LOT.</p>
<p>Text generation, image generation, audio &amp; speech generation and even app generation. It feels like we are finally living in the future. We have unlocked immense capability, productivity and even creativity with these new tools, and everyone is scrambling to find the best way to use them. But that's the question: <strong>How do we use them?</strong></p>
<p>AI's burgeoning potential is fundamentally changing how we interact with computers. In this article, we will explore the following:</p>
<ul>
<li><p>How AI multimodality turns human-computer interaction on its head.</p>
</li>
<li><p>How Copilots have conquered the app space.</p>
</li>
<li><p>How multimodality is changing the way we communicate with machines.</p>
</li>
<li><p>What impact generative AI and agents will have on the future UIs?</p>
</li>
</ul>
<p>Join me and find out how UI can harness this new-found AI power.</p>
<p>Let's dive in!</p>
<h2 id="heading-understanding-the-user-interface">Understanding the User Interface</h2>
<p>UIs have existed ever since we started building machines. Leonardo da Vinci's contraptions needed levers and straps to operate. Watches had hands to display the time and a crown to set it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893161798/801a69d5-e098-4e75-9f48-187f074aa8d3.png" alt /></p>
<p>So, what are user interfaces exactly?</p>
<blockquote>
<p><em>"UIs are the space where interactions between humans and machines occur."</em> <a target="_blank" href="https://en.wikipedia.org/wiki/User_interface">wikipedia</a></p>
</blockquote>
<p>UIs act as a bridge between humans and the capabilities of their machines. The purpose of UI is to add <strong>simplicity and efficiency</strong> to human-machine interactions, helping users <strong>achieve their goals</strong> and make the experience <strong>engaging and accessible</strong>.</p>
<p>And as the machines evolved, so did the UIs.</p>
<p>I made a short storybook (with pictures 🖼) that outlines the evolution of UIs thus far:</p>
<p><strong>40s:</strong> Programming the first computers, such as the ENIAC, required laboriously adding and removing vacuum tubes from the machine. One would look at various lights on the computer to read the machine's output.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893173113/efcfa008-3e62-4dcb-8abe-29d3e2103146.jpeg" alt /></p>
<p><strong>50s:</strong> Punch cards allowed the user to write programs away from the - often deafening - computer room. This also opened up collaboration and asynchronous work.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893178449/bac6e314-63fc-4c19-a201-cd3cc8c0559b.jpeg" alt /></p>
<p><strong>60s:</strong> Invention of the mouse. The Unix Command Line Interface (CLI) dates back to 1969 and remains one of the main ways we use computers today.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893235477/2d9c34a0-4e0b-416a-a6d6-f1904a963231.png" alt /></p>
<p><strong>70s:</strong> Xerox PARC developed the Alto computer with the first graphical interface with icons. This ushered in the era of Video Display Terminals (VDTs). As a 21st-century citizen, I don't need to tell you how important screens will be.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893248104/aa88acdd-c441-4611-93fb-473efff8e51b.jpeg" alt /></p>
<p><strong>1983</strong> Apple released the first commercial computer with a GUI, the Lisa.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893252741/cdf40cdf-6be6-414c-b63c-898cd98232d3.jpeg" alt /></p>
<p><strong>1985:</strong> Microsoft releases Windows 1.0, making GUIs mainstream.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893260152/0ed79998-bb8b-4f6b-9112-f3d3e3483a0d.png" alt /></p>
<p><strong>2007:</strong> Apple releases iPhone, which introduces multi-touch GUI.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893346943/061b24ae-a5f4-45f2-bfd0-b88a27ddd42f.png" alt /></p>
<p><strong>2010-2016:</strong> Siri, Alexa, and Google Assistant brought voice interfaces to the masses.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893377087/9f4305ca-c040-47e5-8b3a-18f73fb7cb90.png" alt /></p>
<p>I paint this evolution in broad strokes, yet you can see how our communication with our machines has changed. What started by pulling levers, pushing buttons and replacing parts turned into clicking, tapping and speaking. Looking for lights around the device and decoding punch cards turned into icons, tables, text and images.</p>
<p>As our machines become more capable, the need for higher bandwidth communication increases. The last 100 years gave us a continuous increase in resolution, both on the output from and the input to the machine.</p>
<h2 id="heading-understanding-humans">Understanding Humans</h2>
<p>Tony Stark's digital assistant, J.A.R.V.I.S., from the 2008 movie "Iron Man" painted a beautiful picture of what would be possible if machines could understand us.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893462276/83b6ebf1-c10e-4038-a9ab-7a9472be308a.png" alt /></p>
<p>Iron Man and sci-fi stories like it, no doubt, inspired the 2010s incarnation of voice assistants like Siri, Alexa and Google Assistant. Despite the NLP technology that went into creating them, they didn't live up to that dream. It felt more like triggering keywords than a conversation.</p>
<p>When I first played around with GPT -3 in 2022, what struck me immediately was how well the computer was able to grasp what I <strong>meant to say</strong>. For the first time, the machine did the heavy lifting in the conversation. My input could have lousy spelling or incomplete sentences, but the computer filled the gaps with astounding competence. I knew immediately that Large Language Models (LLMs) will fundamentally change how we interact with computer systems.</p>
<p>Up to now, user interfaces were the answer to the question: <strong>"How do humans communicate with machines?"</strong></p>
<p>From now on, user interfaces will answer the question: <strong>"How can machines communicate like humans?"</strong></p>
<p>The first answer to this new question was undoubtedly <strong>chat</strong>.</p>
<h2 id="heading-the-rise-of-the-copilot">The Rise of the Copilot</h2>
<p>GPT -3 has been around since June 2020 but only really kicked off when ChatGPT was released in late 2022. Why was ChatGPT popular when GPT -3 wasn't? Yes, Reinforcement Learning with Human Feedback (RLHF) played a massive part in making the output more useful. But the other part of the equation is the <strong>chat interface.</strong></p>
<p>From texting in the 90s to MSN in the 2000s to Facebook in the 2010s, chat has proven to be one of the mainstays of human communication. Asynchronous, fast, back-and-forth, and ever more expressive. The perfect medium to test the machine's new human-to-human level communication skills.</p>
<h3 id="heading-chat-bots-andamp-customer-service">Chat Bots &amp; Customer Service</h3>
<p>A chat box inside the app connects you to a support agent where you need them. Placing support in the product itself can give the support agent valuable context on your problem. Pages you visited, actions you performed, the steps you got stuck on. Chat's back-and-forth nature helps zero in on the issue and solve it quickly.</p>
<p>But this time, no human is sitting on the other end of the line. A domain expert who can ingest all your browsing context as you go and immediately synthesize an answer to your question. No re-connecting to technical support, no language barriers, no wait time.</p>
<p>All this is now possible at scale. Offering this level of service dramatically increases the product's value and used to cost companies millions. Cutting costs while increasing value; no wonder incumbents are jumping at the opportunity.</p>
<p>Copilots started as chatbots. But now they are so much more.</p>
<h3 id="heading-workspace-copilot">Workspace Copilot</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893476381/60a89f9b-a89d-42ce-9e20-d43114d40f4d.png" alt /></p>
<p>Copilots take support to the next level:</p>
<ul>
<li><p>Support concerns itself with <strong>user issues</strong> and is by nature <strong>reactive</strong>.</p>
</li>
<li><p>The co-pilot is concerned with <strong>user objectives</strong> and is <strong>proactive</strong>.</p>
</li>
</ul>
<p>This is a fundamental change in service. The copilot is your personal expert. Always available, always clued-in on what you're doing and trying to do.</p>
<p><a target="_blank" href="https://github.com/features/copilot">GitHub Copilot</a> reads your code and predicts your next move. Just hit "Tab" to accept. This seemingly simple workflow saves developers countless hours. Its adoption in the developer community speaks for itself.</p>
<p><a target="_blank" href="https://blogs.microsoft.com/blog/2023/03/16/introducing-microsoft-365-copilot-your-copilot-for-work/">Microsoft 365 Copilot</a> and <a target="_blank" href="https://www.theverge.com/2023/9/19/23878999/google-bard-ai-chatbot-gmail-docs-drive-extensions">Google Bard</a> now plug directly into Documents, Sheets, Emails and more. Don't know how to plot a graph in Excel? Microsoft's copilot does. Want to know what's been going on in your inbox? Summarize it in Bard.</p>
<p>The Google search engine changed how we interact with information. Memorizing facts lost its value in a "just google it" world.</p>
<p>Copilots are starting to shift the way we use applications. Why take an Excel course if you can "just copilot it"?</p>
<h3 id="heading-power-to-the-users">Power to the Users</h3>
<p>Again, we are shifting away from domain-specific, time-consuming, hard-to-learn skills. What matters in this new world of applications is user intent. The struggle of UI was to expose the right amount of surface area to the user such that a balance is struck between capability and ease of use. But now that we have AI as a translation layer between user intent and tool use, <strong>the user no longer needs to understand the tool itself</strong>.</p>
<p>App creators can now build tools of arbitrary complexity without regard for UX. And that is a good thing. Let me explain.</p>
<p>How many times have you used <code>ipconfig</code> in your terminal? Do you understand its output? I am speaking to a technical crowd, so perhaps you do. But does Carol from accounting who is wondering why her internet stopped working?</p>
<p>In our daily lives, we use only a fraction of what our machines are capable of. Many system features are not even accessible through menus because building UI is complex and expensive. But a copilot with access to those features can understand how and when to use them so Carol doesn't have to. This effectively increases the user's power without having to teach them or build UI. With a copilot, every user is now a superuser.</p>
<p>"Okay", I hear you ask, "What's the endgame here? Will the UIs of the future all converge to chat boxes?"</p>
<p>Oh, let me tell you, we haven't even started.</p>
<h2 id="heading-multimodality">Multimodality</h2>
<p>Let's take a step back to see where we are.</p>
<ol>
<li><p>UIs try to solve human-machine interaction.</p>
</li>
<li><p>With LLMs, machines can now understand humans.</p>
</li>
<li><p>Chat's back-and-forth style allows humans to use natural language to describe their intent to a machine.</p>
</li>
<li><p>Copilots can use tools to perform actions. They combine context with user intent to produce solutions.</p>
</li>
</ol>
<p>In the history section, I introduced the idea of increasing bandwidth. <strong>The more machines can do, the more ways we need to tell it what to do</strong>. However, AI's ability to translate is not restricted to human languages. It is becoming increasingly potent when translating between many different modalities:</p>
<ul>
<li><p>Text</p>
</li>
<li><p>Image</p>
</li>
<li><p>Video</p>
</li>
<li><p>Audio</p>
</li>
<li><p>Code</p>
</li>
</ul>
<p>The internet of today can barely be compared to its early days. It started out with only text. Soon, images, audio and videos joined the party, creating today's rich multimedia landscape. This bandwidth evolution is not only measured in bytes but also in meaning.</p>
<p>Learning to cook a steak from a YouTube video is far superior to reading only text. How long it takes, how brown it should get before it is taken out, and what sound it should make when it hits the pan. All this information is vital to the experience of cooking a steak, and we are now able to communicate that information due to increased bandwidth and meaning. AI is going through the same evolution now.</p>
<h3 id="heading-input">Input</h3>
<p>Machines can now understand us and the world, regardless of how we communicate. Presenting our intent using images or video is likely more effective than text.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893508983/9ccbd6f9-3ea3-4c0c-b6d5-472dd1671906.png" alt /></p>
<p>OpenAI is fully embracing <a target="_blank" href="https://openai.com/blog/chatgpt-can-now-see-hear-and-speak">multimodality</a>. Snap a photo to point out your issue. Speak rather than write. Imagine how many problems have been solved by simply pointing and saying "This thing".</p>
<p>There is still untapped potential. For example, when conveying emotion to other humans, <em>what</em> we say is only a tiny fraction of the message. The rest is contained in the body language, the tone of voice and the facial expressions. What would an AI therapist be like if they could understand that? How effective could an AI salesperson be if they can pick up those cues? A human needs a different response based on their state of mind. AI can intelligently generate a more suitable response when it can capture a broader spectrum of our expression.</p>
<h3 id="heading-output">Output</h3>
<p>ChatGPT can now also reply with images. It can write and run code. It can generate speech.</p>
<p>All of these new features are a MASSIVE deal for accessibility and convenience. In the car and can't use your hands? No problem. It will read the response out to you. Need to know what a carburettor looks like? No problem. It will create an image.</p>
<p>ChatGPT blew my mind because it could interpret my human drivel. But now, AI can translate that drivel to image, audio, video, and even fully functioning apps. The AI's superpower of translation presents an entirely new paradigm of human-machine interaction.</p>
<h3 id="heading-multimodal-ui">Multimodal UI</h3>
<p>The AI-driven multimodal shift opens up a world of possibilities in UI design. But what do these advancements mean for the existing UIs?</p>
<p><strong>Implications:</strong></p>
<ul>
<li><p><strong>Apps Becoming Multimodal</strong>: As AI becomes proficient in interpreting and responding through various modes, apps will follow suit. Expect a future where applications communicate via text, voice, image, and gestures.</p>
</li>
<li><p><strong>More Natural Interactions</strong>: AI's proficiency in human-like dialogues creates a more intuitive and engaging user experience. Soon, interacting with digital devices could feel as natural as conversing with a friend.</p>
</li>
<li><p><strong>Increased Accessibility</strong>: Multimodal interactions can make technology more accessible. By catering to different input and output modes, apps can cater to a wide range of user abilities, making digital experiences inclusive.</p>
</li>
</ul>
<h2 id="heading-adaptive-interfaces">Adaptive Interfaces</h2>
<p>As we move away from the one-size-fits-all paradigm, we enter the realm of adaptive interfaces.</p>
<p>I like to think of it as <strong>dark mode but for everything</strong>. Did you notice how apps sometimes know your preferences and adjust their theme to match? It is often labelled as "use device theme" or similar.</p>
<p>Now, imagine you have a preferred font, a preferred layout, and a preferred colour palette. Adaptive interfaces are not limited to looks. Content could change based on user preferences. Read the docs in the style of your favourite writer, and populate a website with imagery that you enjoy. The way you browse the web is viewed through your preferred set of glasses.</p>
<p>This is, however, only the user side. AI, coupled with vast amounts of data about user behaviours, can craft unique UI experiences for each user. If you are a business offering many different services, how great would it be if your website showed precisely what a specific user needs? No more, no less.</p>
<p><strong>Implications:</strong></p>
<ul>
<li><p><strong>Personalized Experience</strong>: From preferred colour schemes to the organization of information, every aspect of the UI could be adapted to align with the user's preferences and habits.</p>
</li>
<li><p><strong>Contextual Adaptation</strong>: By understanding the context - like device used, time of day, location and even mood of the user, AI might adapt the UI dynamically. Imagine a UI that switches to voice output when it detects the user is driving.</p>
</li>
<li><p><strong>Predictive Capabilities</strong>: A significant advantage of AI is that it can anticipate user needs. The UI of tomorrow might proactively adjust itself, providing the user with what they need before they even have to ask.</p>
</li>
</ul>
<h2 id="heading-representative-interfaces">Representative Interfaces</h2>
<p>Taking adaptive interfaces one step further, AI could serve as an extension of the user, becoming their digital representative and an intermediary between them and the software.</p>
<h3 id="heading-agent-basics">Agent basics</h3>
<p>If you've never heard of AI agents, here's the rundown:</p>
<ul>
<li><p>Agents act autonomously towards a user-defined goal.</p>
</li>
<li><p>To create a basic agent, one adds long-term memory and tools to an LLM.</p>
</li>
<li><p>The LLM can recursively prompt itself to move towards a goal.</p>
</li>
</ul>
<p>Getting even closer to a human-feeling assistant, agents give AI some autonomy to complete tasks on your behalf. But why stop at one?</p>
<h3 id="heading-multi-agent-systems">Multi-agent systems</h3>
<p>Projects like <a target="_blank" href="https://github.com/OpenBMB/ChatDev">ChatDev</a> allow you to compose a team of AI agents to form an organization that works to complete your objective.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893531738/cfdb7b27-a066-43b1-bdfb-dd0da3c8e499.png" alt /></p>
<p>If that sounds absolutely bonkers, that's because it is. You can recruit an army of AI specialists that work for you. Assigning different roles to every agent even improves the output because they constantly check each other's work from different perspectives. Even Microsoft is betting on this technology with <a target="_blank" href="https://github.com/microsoft/autogen/tree/main">AutoGen</a> that just released. Keep your eyes out for that.</p>
<p>Copilots have given us a glimpse of this future by translating user intent into action. Agents take this to a whole new level. With agent systems like ChatDev, AutoGen and <a target="_blank" href="https://www.hyperwriteai.com/">HyperWrite</a> that can autonomously complete tasks using your browser, will we no longer need to interact with apps ourselves? Where does this leave developers?</p>
<h3 id="heading-apps-in-a-world-of-agents">Apps in a World of Agents</h3>
<p>Landing pages are made for humans. Sure, agents are starting to understand how to use our web pages, but why build web pages if agents can just as quickly parse an API? Why spend time and money creating a web page when the user's adaptive interface already displays your info?</p>
<p>If you are developing a software service for agents, the only thing that matters is how well the agent understands you. If we strip away landing pages and fancy layouts, what is left is API design and docs.</p>
<p>In a way, this is quite freeing. Focus on creating new functionality and let AI determine how it could benefit the user.</p>
<p>On the other hand, website creation tools are becoming increasingly advanced and easy to use. Vercel's <a target="_blank" href="https://v0.dev/explore">V0</a> creates beautiful React components from a text prompt. I doubt it will be long before a single text prompt and a link to the API can create an entire frontend. If full site generation is cheap and easy, there's no reason to not have content optimized for humans and agents alike.</p>
<p>I wonder if there will be SEO for agents 🤔 food for thought...</p>
<h3 id="heading-ui-in-a-world-of-agents">UI in a World of Agents</h3>
<p>Since the beginning of this article, we have taken gigantic leaps towards our JARVIS dream. With our current terminology, Tony Stark's assistant would be categorized as an agent. Using agents is such a powerful idea that I see agents as the primary way we interact with the digital world in the future. But what will that interaction look like? What's the surface area between the human and the agent?</p>
<p>If you read this article from the beginning, your bells should be ringing by now. One of the next significant UI space changes will be designing for human-agent interaction. If agents are the primary way of using a computer, they would be more akin to an operating system than an app.</p>
<p>In contrast to a copilot, an agent may choose more broadly which tool to use to complete the user objective. It could even recruit other agents to complete a task. I suspect how much of this interaction will be known to the user will depend on our trust in these agent systems. There's no need to micro-manage agent systems if we know they're reliable. Speaking of reliability, who is building this?</p>
<p>Although we could see a completely new player in this race, I predict that incumbent computing platforms will be the first to integrate these features into devices we already use.</p>
<p>Meta has made a big bet on virtual and augmented reality and is now developing representative interfaces for users' digital avatars. <a target="_blank" href="https://about.fb.com/news/2023/09/introducing-ai-powered-assistants-characters-and-creative-tools/">In Meta's recent announcement</a>, they say that you can train an AI to act and speak like you so that people can still interact with your digital clone, even if you are not online. That seems sorely needed based on the small number of people populating their VR worlds. But jokes aside, these are the first steps towards representative interfaces, and I'm excited to see what comes next.</p>
<p>They also showcased how AI integrates into Instagram and Facebook Messenger and their new AI glasses in partnership with RayBan, which gives us a glimpse of how multimodal AI interfaces can be applied to wearable tech. Apple is calling Meta's bet on VR and AR with its creepy-eyed <a target="_blank" href="https://www.apple.com/apple-vision-pro/">Vision Pro Headset</a> which will release early next year. Interesting to see here is how Meta and Apple create different interfaces for their respective headsets. UI for sunglasses that are taken out and about varies drastically from a Meta Quest gaming VR headset or Apple's media &amp; workspace headset. Apple focuses on hand gestures, while gaming-focused Quest uses controllers. I am interested to see what multimodal AI will bring to the table.</p>
<p><a target="_blank" href="https://techcommunity.microsoft.com/t5/windows-it-pro-blog/copilot-in-windows-and-new-cloud-pc-experiences-coming-to/ba-p/3933653">Windows 11</a> also just launched a Windows copilot, integrated directly into the OS. It's still early, but we will soon see agentic interactions directly from the desktop.</p>
<h3 id="heading-agents-summary">Agents Summary</h3>
<p>The potential of AI agents has just started to be realized in UI design. Here's a recap of the key ideas covered:</p>
<ul>
<li><p><strong>The Emergence of AI Agents</strong>: AI agents act autonomously towards user-defined goals, effectively becoming digital extensions of the users. They utilize AI's rich translation abilities to interact with the digital world on the user's behalf.</p>
</li>
<li><p><strong>AI as Middlemen</strong>: Users may no longer need to interact with complex GUIs. Instead, their AI agent will interact with the software on their behalf based on their preferences and instructions.</p>
</li>
<li><p><strong>Reduced Learning Curve</strong>: With AI representatives, users may not need to learn how to use complex software, saving time and making experiences more enjoyable.</p>
</li>
<li><p><strong>A Universal Interface</strong>: Ultimately, AI representatives could become a universal interface. No matter what software or application a user interacts with, their AI can serve as their primary user interface, greatly simplifying digital interactions.</p>
</li>
<li><p><strong>Towards Multi-agent Systems</strong>: Projects like ChatDev, AutoGen, and HyperWrite are pushing the boundaries by enabling composite teams of AI agents. Users can now create personalized cohorts of AI assistants with different specialities.</p>
</li>
<li><p><strong>Changing a Developer's Role</strong>: As AI agents become more common, we'll likely see an emphasis on API design and documentation. The easier an agent can understand a service, the more accessible and effective that service becomes to users.</p>
</li>
<li><p><strong>Shaping UIs in an Agent-centric World</strong>: As AI agents integrate into our digital interactions, there will be a new challenge in designing user interfaces tailored for human-agent interaction.</p>
</li>
<li><p><strong>The Future of Consumer Tech</strong>: Incumbent tech giants like Meta already invest heavily in representative interfaces, indicating a movement toward more personalized, autonomous technology experiences.</p>
</li>
<li><p><strong>Simplified Interactions and Enhanced Accessibility</strong>: AI agents could ultimately simplify digital interactions, providing a universal interface that adapts to the user's unique preferences and needs. The representative interfaces of the future hold the promise of a more accessible, intuitive digital world.</p>
</li>
</ul>
<p>We're just starting on this exciting journey towards fully autonomous, AI-powered digital experiences. Hold on tight as things are about to get even more fascinating.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The evolution of AI technologies presents an exciting transformation for user interfaces. What started as simple levers and knobs has now branched into chat boxes, copilots, adaptive and representative interfaces. The multimodal abilities of AI are set to redefine how we interact with our digital world.</p>
<p>One thing is for sure: as AI continues honing its human-like communicative skills, the landscape of user engagement will undoubtedly undergo a dramatic shift. Our machines have begun to understand us better, and sooner than we think, they'll communicate like us, too, setting the stage for a future of intelligent actors directed by natural human expression.</p>
<h2 id="heading-the-end">The End</h2>
<p>Thanks for reading :) This is my first time writing an idea-based article. I hope it was exciting and thought-provoking.</p>
<p>What novel interfaces have you seen spring up recently? How do you think AI will change our UIs? Let me know in the comments!</p>
]]></content:encoded></item><item><title><![CDATA[How I Reduced Onboarding Time By 90% With Document Scanning]]></title><description><![CDATA[The Problem
CoverCraft is a web app that writes customized cover letters using AI. With this project, I am trying to stretch my skills to make a fully functioning SaaS product with a top-notch user experience. I want users to feel taken care of from ...]]></description><link>https://blog.noelcserepy.com/how-i-reduced-onboarding-time-by-90-with-document-scanning</link><guid isPermaLink="true">https://blog.noelcserepy.com/how-i-reduced-onboarding-time-by-90-with-document-scanning</guid><category><![CDATA[AI]]></category><category><![CDATA[OCR ]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[UX]]></category><dc:creator><![CDATA[Noël Cserépy]]></dc:creator><pubDate>Tue, 19 Sep 2023 14:54:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695135112448/a2c8c74e-57ab-416b-9b81-04335eab9d2b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-problem">The Problem</h2>
<p>CoverCraft is a web app that writes customized cover letters using AI. With this project, I am trying to stretch my skills to make a fully functioning SaaS product with a top-notch user experience. I want users to feel taken care of from start to finish, the full 5-star service.</p>
<iframe src="https://giphy.com/embed/l49JAE75MttFHE7MQ" width="240" height="240" class="giphy-embed"></iframe>

<p>Previous user flow:</p>
<ol>
<li><p>Sign in / Sign up (&lt;1 minute).</p>
</li>
<li><p>Enter CV information in the "CV" tab (5-15 minutes).</p>
</li>
<li><p>Add a job description in the "Jobs" tab (&lt;1 minute).</p>
</li>
<li><p>Get your cover letter (&lt;1 minute).</p>
</li>
</ol>
<p>I was happy with the speed at which existing users can create new cover letters. With their CV added, users could repeat step 3 and add job descriptions to generate more cover letters in less than a minute.</p>
<p><strong>The problem is getting them to that point</strong>.</p>
<p>The biggest hurdle to start using CoverCraft is the tedious process of entering your CV information by hand. It can take up to 10 minutes (or more if you have lots of experience and education). The amount of investment required from the user is highly frontloaded. Wouldn't it be nice if all your CV data appeared in the app without filling out forms?</p>
<blockquote>
<p>A quick note from Noël: I will not go into as much detail as I usually would because there are many lines of code and insufficient attention span. Consider it more of a bird's-eye view of the architecture. Please let me know if you want me to cover certain parts in more detail! :)</p>
</blockquote>
<h2 id="heading-the-idea">The Idea</h2>
<p>I want 3 things:</p>
<ol>
<li><p><strong>I want new users to be able to upload their existing CV and pull out all the relevant information in one go.</strong> This will get users started extremely quickly, significantly reducing the time to create their first cover letter.</p>
</li>
<li><p><strong>I want users to be able to edit the scanned information step-by-step.</strong> I got inspired by how <a target="_blank" href="https://www.typeform.com/">TypeForm</a> lets you fill out forms one field at a time. This guided approach feels much more manageable than slogging through an entire page of forms simultaneously. Bite-sized, pre-filled forms, that's the goal.</p>
</li>
<li><p><strong>I want to make this feature available pre-signup.</strong> Once users invest a <em>short</em> amount of time uploading and editing their CV, they are more likely to follow through with a signup. I could have locked this feature behind a paywall to make the premium subscription more attractive, but my focus is to get users and feedback to improve the product.</p>
</li>
<li><p><strong>I want CV data seamlessly synced across auth boundaries.</strong> This user flow has to be seamless to feel good. Once the user logs in, everything should be available and ready to go. Users should also have access to this feature post-signup.</p>
</li>
</ol>
<h2 id="heading-routes-amp-pages">Routes &amp; Pages</h2>
<p>Currently, CoverCraft is split into two main routes: the <strong>homepage</strong> "/" and a <strong>dashboard</strong> "/dashboard". The dashboard is only accessible to signed-in users. Because I want the upload feature to be accessible to users, regardless of auth status, I created a new route, "/upload".</p>
<p>The upload route will use the state to render different components for every step of the user flow. The components will be rendered in the following order:</p>
<ol>
<li><p>Upload CV</p>
</li>
<li><p>Personal Info</p>
</li>
<li><p>Education</p>
</li>
<li><p>Experience</p>
</li>
<li><p>Skills</p>
</li>
<li><p>Done</p>
</li>
<li><p>Pricing</p>
</li>
</ol>
<h2 id="heading-upload-cv">Upload CV</h2>
<p>I thought about implementing my own scanning functionality, but something tells me this already exists. I found that <a target="_blank" href="http://Eden.ai">Eden.ai</a> offers a service for resume parsing. I can extract structured resume information from a PDF file with a simple HTTP request. Perfect!</p>
<p>But first, we need the user's CV file. I used <a target="_blank" href="https://www.npmjs.com/package/react-dropzone">react-dropzone</a> to create a drag'n'drop input field.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UploadCV</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [file, setFile] = useState&lt;File&gt;();
    <span class="hljs-keyword">const</span> [cvData, setCvData] = useAtom(cvDataAtom);
    <span class="hljs-keyword">const</span> [, setCurrentStep] = useAtom(currentStepAtom);

    <span class="hljs-keyword">const</span> onDrop = useCallback(<span class="hljs-function">(<span class="hljs-params">acceptedFiles: File[]</span>) =&gt;</span> {
    setFile(acceptedFiles[<span class="hljs-number">0</span>]);
    }, []);

    <span class="hljs-keyword">const</span> { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });

    <span class="hljs-keyword">const</span> upload = <span class="hljs-keyword">async</span> (e: MouseEvent) =&gt; {
        e.stopPropagation();
        e.preventDefault();
        <span class="hljs-keyword">if</span> (!file) <span class="hljs-keyword">return</span>;
        setLoading(<span class="hljs-literal">true</span>);

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> form = <span class="hljs-keyword">new</span> FormData();
            form.append(<span class="hljs-string">"file"</span>, file);

            <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/parseResume"</span>, {
            method: <span class="hljs-string">"POST"</span>,
            body: form,
            });

            <span class="hljs-keyword">const</span> data = (<span class="hljs-keyword">await</span> res.json()) <span class="hljs-keyword">as</span> IParseResponse;

            setCvData(data.data);
            setLoading(<span class="hljs-literal">false</span>);
            setCurrentStep(<span class="hljs-number">1</span>);
        } <span class="hljs-keyword">catch</span> (e) {
            <span class="hljs-built_in">console</span>.error(e);
        }
    };

    <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">// HTML stuff is too long to fit. </span>
    <span class="hljs-comment">// You're not missing out on much. </span>
    <span class="hljs-comment">// It's simply an &lt;input&gt; element with dropzone props and conditional rendering.</span>
    <span class="hljs-comment">// If a file is in state -&gt; show the upload button.</span>
    <span class="hljs-comment">// Upload button calls the upload function. Bob's your uncle.</span>
)}
</code></pre>
<p>I add the file to the components state using <code>useState()</code> and then let the user hit "upload".</p>
<p>I could make a direct HTTP request to <a target="_blank" href="http://Eden.AI">Eden.AI</a>'s API, but that would reveal my API key. Therefore, I must create a NextJS API route to handle this request. Notice how I use <code>FormData</code> to send the file over http. This allows us to omit the <code>Content-Type</code> as the protocol will infer it. I fiddled around too long trying to figure this out, so there you go. :)</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { NextResponse, <span class="hljs-keyword">type</span> NextRequest } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Location, APIResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"./types"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> CV } <span class="hljs-keyword">from</span> <span class="hljs-string">"~/store"</span>;

<span class="hljs-keyword">const</span> URL = <span class="hljs-string">"https://api.edenai.run/v2/ocr/resume_parser"</span>;
<span class="hljs-keyword">const</span> KEY = process.env.EDENAI_API_KEY;
<span class="hljs-keyword">const</span> APP_URL = process.env.APP_URL;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> IFullAPIResponse {
  <span class="hljs-string">"eden-ai"</span>: {
    extracted_data: APIResponse;
    success: <span class="hljs-built_in">boolean</span>;
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> IRelevantData {
  personalInfos: {
    firstName: <span class="hljs-built_in">string</span>;
    lastName: <span class="hljs-built_in">string</span>;
    email: <span class="hljs-built_in">string</span>;
    phone: <span class="hljs-built_in">string</span>;
    selfSummary: <span class="hljs-built_in">string</span>;
    address: Location;
  };
  education: {
    description: <span class="hljs-built_in">string</span>;
    school: <span class="hljs-built_in">string</span>;
    title: <span class="hljs-built_in">string</span>;
    gpa: <span class="hljs-built_in">string</span>;
    start: <span class="hljs-built_in">string</span>;
    end: <span class="hljs-built_in">string</span>;
    location: <span class="hljs-built_in">string</span>;
  }[];
  experience: {
    company: <span class="hljs-built_in">string</span>;
    description: <span class="hljs-built_in">string</span>;
    title: <span class="hljs-built_in">string</span>;
    start: <span class="hljs-built_in">string</span>;
    end: <span class="hljs-built_in">string</span>;
    location: <span class="hljs-built_in">string</span>;
    industry: <span class="hljs-built_in">string</span>;
  }[];
  skills: <span class="hljs-built_in">string</span>[];
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> IParseResponse {
  success: <span class="hljs-built_in">boolean</span>;
  data: CV;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">request: NextRequest</span>) </span>{
  <span class="hljs-keyword">if</span> (!KEY) {
    <span class="hljs-keyword">return</span> NextResponse.json({ success: <span class="hljs-literal">false</span>, message: <span class="hljs-string">"No key"</span> });
  }
  <span class="hljs-keyword">if</span> (!APP_URL) {
    <span class="hljs-keyword">return</span> NextResponse.json({ success: <span class="hljs-literal">false</span>, message: <span class="hljs-string">"No app url"</span> });
  }
  <span class="hljs-keyword">if</span> (!request.headers.get(<span class="hljs-string">"referer"</span>)?.includes(APP_URL)) {
    <span class="hljs-built_in">console</span>.log(request.headers.get(<span class="hljs-string">"referer"</span>));
    <span class="hljs-built_in">console</span>.log(APP_URL);
    <span class="hljs-keyword">return</span> NextResponse.json({ success: <span class="hljs-literal">false</span>, message: <span class="hljs-string">"Invalid referrer"</span> });
  }

  <span class="hljs-keyword">const</span> form = <span class="hljs-keyword">await</span> request.formData();
  form.append(<span class="hljs-string">"providers"</span>, <span class="hljs-string">"hireability"</span>);

  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(URL, {
    method: <span class="hljs-string">"POST"</span>,
    headers: {
      authorization: <span class="hljs-string">`Bearer <span class="hljs-subst">${KEY}</span>`</span>,
    },
    body: form,
  });

  <span class="hljs-keyword">const</span> scan = (<span class="hljs-keyword">await</span> res.json()) <span class="hljs-keyword">as</span> IFullAPIResponse;
  <span class="hljs-keyword">const</span> extracted = scan[<span class="hljs-string">"eden-ai"</span>][<span class="hljs-string">"extracted_data"</span>];
  <span class="hljs-keyword">const</span> relevantData = {
    id: <span class="hljs-string">"1"</span>,
    personalInfo: {
      firstName: extracted.personal_infos.name.first_name,
      lastName: extracted.personal_infos.name.last_name,
      email: extracted.personal_infos.mails[<span class="hljs-number">0</span>],
      location: extracted.personal_infos.address.city,
    },
    education: extracted.education.entries.map(<span class="hljs-function">(<span class="hljs-params">entry</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> {
        degree: <span class="hljs-string">`<span class="hljs-subst">${entry.title || <span class="hljs-string">""</span>}</span> in <span class="hljs-subst">${entry.description}</span>`</span>,
        school: entry.establishment,
        title: entry.title,
        description: entry.description,
        achievements: [],
        startDate: entry.start_date,
        endDate: entry.end_date,
      };
    }),
    experience: extracted.work_experience.entries.map(<span class="hljs-function">(<span class="hljs-params">entry</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> {
        title: entry.title,
        company: entry.company,
        startDate: entry.start_date,
        endDate: entry.end_date,
        description: entry.description,
        achievements: [],
      };
    }),
    skills: extracted.skills.map(<span class="hljs-function">(<span class="hljs-params">entry</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> entry.name;
    }),
  } <span class="hljs-keyword">as</span> CV;

  <span class="hljs-keyword">if</span> (res.ok) {
    <span class="hljs-keyword">return</span> NextResponse.json({
      success: <span class="hljs-literal">true</span>,
      data: relevantData,
    } <span class="hljs-keyword">as</span> IParseResponse);
  }
  <span class="hljs-keyword">return</span> NextResponse.json({
    success: <span class="hljs-literal">false</span>,
    data: relevantData,
  } <span class="hljs-keyword">as</span> IParseResponse);
}
</code></pre>
<p><a target="_blank" href="http://Eden.AI">Eden.AI</a> returns structured data, but I must only extract the relevant information. This would require writing type definitions and mapping the API output to new objects. I would have put all this logic in a separate file anyway, so using an API route makes our code a bit cleaner.</p>
<p>From there, we return the data in our response and handle the rest in the UploadCV component.</p>
<h2 id="heading-handling-state-persisting-data">Handling State / Persisting Data</h2>
<p>I can't simply store the data using <code>useState()</code>. The problem is that all the data is lost when the component re-renders. The data will be gone if a user leaves the "/upload" route to sign in or go to the dashboard.</p>
<p>Another option would be to save it in the Firestore database. The problem is that the user may not be signed in yet and, therefore, has no existing document in the "users" collection. Besides, I want to limit database access to authenticated users for security reasons.</p>
<p>My solution is to persist the scan data in the user's local storage. The browser will remember the scanned data and the flow step across sessions. This way, users can leave and return to complete the flow without losing their work. Once the user signs in, I can check the localStorage for scan data and copy it into my database.</p>
<p>I am using <a target="_blank" href="https://jotai.org/">Jotai</a> for state management already, and luckily there is a way to save state Jotai state to localStorage using <a target="_blank" href="https://jotai.org/docs/utilities/storage">atomWithStorage</a>. It works just like you would expect. Create an atom using <code>atomWithStorage</code> and call <code>useAtom</code> to get the getter and setter functions. Jotai will keep track of the <strong>current upload step</strong> and the <strong>scanned data</strong>.</p>
<h2 id="heading-forms-forms-forms">Forms forms forms</h2>
<p>Steps 2-5 allow the user to edit the extracted data. I mainly used the same forms as on the dashboard. There's only a little to say here besides that I use <a target="_blank" href="https://www.react-hook-form.com/api/">react-hook-form</a> to manage the forms.</p>
<p>Clicking the "Next" button increments the <code>currentStep</code> state, which causes the parent component to render the next page.</p>
<h2 id="heading-crossing-the-auth-boundary">Crossing the auth boundary</h2>
<p>Once the user reaches step 6, they complete the forms and populate the local storage state with all the correct information. Here, there are several paths a user could follow based on their login status.</p>
<ol>
<li><p>If they're <strong>logged in</strong>, I can simply navigate them to the dashboard.</p>
</li>
<li><p>If they're <strong>not logged in</strong>, I can prompt them to create an account by showing the pricing page.</p>
</li>
</ol>
<p>The trick here is that the data remains in localStorage until cleared. The "/dashboard", "/login", and "/upload" routes render entirely different components, but navigating from one to another won't make the user lose their work. So, how do we save the data once the user logs in?</p>
<p>On the layout of the dashboard page, I create a <code>useEffect()</code> that will check if the localStorage contains CV data and if all the steps have been completed. If so, I update my Firestore database and set the localStorage data to null. I then open a model to notify users that their data is securely stored and ready to go.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> [cvData, setCvData] = useAtom(cvDataAtom);

useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (cvData) {
        <span class="hljs-keyword">await</span> updateCV(<span class="hljs-string">"1"</span>, cvData, <span class="hljs-literal">true</span>); <span class="hljs-comment">// &lt;- Store data in firebase</span>
        setCvData(<span class="hljs-literal">null</span>);
        setCurrentStep(<span class="hljs-number">0</span>);

        setModal({
            title: <span class="hljs-string">"Welcome to CoverCraft!"</span>,
            elementName: <span class="hljs-string">"Info"</span>,
            message:
                <span class="hljs-string">"Your CV is ready to go! You can view it in the CV tab. To start writing cover letters, just add a job in the Jobs tab."</span>,
        });
}, [])
</code></pre>
<h2 id="heading-done">Done!</h2>
<p>With all that, users can upload their CV from anywhere in the app, logged in or out. Entering information on CoverCraft has never been less tedious!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This differed from my usual format, focusing more on documenting my process than teaching a concept. I hope using an example to share my thought process helps you make decisions for your apps!</p>
<p>Thank you for reading ❤</p>
]]></content:encoded></item><item><title><![CDATA[How I Created a Typing Text Animation with Framer Motion]]></title><description><![CDATA[The Idea
After finishing some client work, just like everyone else and their grandma, I made an AI app 🙄. Mine writes custom cover letters based on the user's CV and a job description.
When I got around to making the homepage, I knew I wanted to mak...]]></description><link>https://blog.noelcserepy.com/how-i-created-a-typing-text-animation-with-framer-motion</link><guid isPermaLink="true">https://blog.noelcserepy.com/how-i-created-a-typing-text-animation-with-framer-motion</guid><category><![CDATA[framer-motion]]></category><category><![CDATA[animation]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[typing]]></category><dc:creator><![CDATA[Noël Cserépy]]></dc:creator><pubDate>Thu, 31 Aug 2023 16:29:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1693498740779/ae4b2237-1fe9-4858-b370-75d7bd0acb92.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-idea">The Idea</h2>
<p>After finishing some client work, just like everyone else and their grandma, I made an AI app 🙄. Mine writes custom cover letters based on the user's CV and a job description.</p>
<p>When I got around to making the homepage, I knew I wanted to make an emotional impact with my hero section. Something relatable that reminds the user of the frustration of writing cover letters themselves. I came up with this. To see the animation in action, visit <a target="_blank" href="http://covercraft.io">covercraft.io</a>.</p>
<iframe src="https://codesandbox.io/embed/text-typing-animation-rtzvgl?fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2Fcomponents%2FA4Layout.tsx&amp;theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<p>The section on the right looks like a Google Doc. It shows how someone is trying to write a cover letter but failing to find the right words. They re-write the first sentence repeatedly, but every attempt is more cringe-worthy than the last.</p>
<p>In this article, I will show how I made this text typing effect with framer motion. I will use a lot of different animation techniques. If you are new or need a refresher on certain topics, they are covered in my previous articles. Here are some links:</p>
<ul>
<li><p><a target="_blank" href="https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion#heading-installing-framer-motion">Installing framer motion</a></p>
</li>
<li><p><a target="_blank" href="https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion#heading-variants">Variants</a></p>
</li>
<li><p><a target="_blank" href="https://blog.noelcserepy.com/creating-keyframe-animations-with-framer-motion">Keyframes</a></p>
</li>
<li><p><a target="_blank" href="https://blog.noelcserepy.com/maximizing-the-power-of-framer-motion-with-useanimationcontrols">useAnimate</a></p>
</li>
</ul>
<p>There is a CodeSandbox at the start and end of the article, so don't worry about copying everything down as you go. Let's get into it!</p>
<h2 id="heading-the-plan">The Plan</h2>
<p>There are 3 parts to this animation:</p>
<ol>
<li><p><strong>The cursor</strong> - The blinky line at the end of the text to show where the cursor is.</p>
</li>
<li><p><strong>The text</strong> - "Dear Hiring Manager" and the awkward sentences that appear letter by letter as if someone was typing.</p>
</li>
<li><p><strong>The layout</strong> - A simplified Google doc to act as a container.</p>
</li>
</ol>
<p>We'll create separate react components for each to keep things organised.</p>
<h2 id="heading-the-cursor">The Cursor</h2>
<p>In essence, it's just a line that comes and goes. We can create a <code>motion.div</code> with a width of <code>1px</code> and add a variant called <code>blinking</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { motion } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;

<span class="hljs-keyword">const</span> cursorVariants = {
  blinking: {
    opacity: [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>],
    transition: {
      duration: <span class="hljs-number">1</span>,
      repeat: <span class="hljs-literal">Infinity</span>,
      repeatDelay: <span class="hljs-number">0</span>,
      ease: <span class="hljs-string">"linear"</span>,
      times: [<span class="hljs-number">0</span>, <span class="hljs-number">0.5</span>, <span class="hljs-number">0.5</span>, <span class="hljs-number">1</span>]
    }
  }
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CursorBlinker</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;motion.div
      variants={cursorVariants}
      animate=<span class="hljs-string">"blinking"</span>
      className=<span class="hljs-string">"inline-block h-5 w-[1px] translate-y-1 bg-slate-900"</span>
    /&gt;
  );
}
</code></pre>
<p>The variant uses keyframes to animate the opacity from <code>0</code> to <code>1</code>. We use four keyframes and define <code>times</code> in the transition. This makes the cursor opacity <code>0</code> for half the duration and <code>1</code> for the other half. Notice how the <code>times</code> for keyframes two and three are 0.5. That means that the opacity will switch <strong>instantly</strong> from <code>0</code> to <code>1</code> instead of fading in and out. This repeats infinitely.</p>
<p>By making the cursor <code>inline-block</code> instead of <code>block</code>, it will appear next to our text instead of below. That way, when we add more letters to our text, the cursor will be <strong>pushed</strong> to the right.</p>
<h2 id="heading-simple-text-animation-with-motion-values">Simple Text Animation with Motion Values</h2>
<p>There are two parts to this. I want "Dear Hiring Manager" to animate only <strong>once</strong> when the component mounts. <strong>After that</strong>, the awkward sentences should animate in on a new line. We will cover that in the next section.</p>
<p>First, let's create the "Dear Hiring Manager" animation. The idea is this:</p>
<ol>
<li><p>Take some text.</p>
</li>
<li><p>Create a count variable.</p>
</li>
<li><p>Animate the count between <code>0</code> and <code>text.length</code>.</p>
</li>
<li><p>Display a substring of text where its length is based on count.</p>
</li>
</ol>
<h3 id="heading-component-setup">Component Setup</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { motion, useMotionValue, useTransform, animate } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;
<span class="hljs-keyword">import</span> CursorBlinker <span class="hljs-keyword">from</span> <span class="hljs-string">"./CursorBlinker"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TextAnim</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> baseText = <span class="hljs-string">"Dear Hiring Manager, "</span>;
  <span class="hljs-keyword">const</span> count = useMotionValue(<span class="hljs-number">0</span>);

  <span class="hljs-keyword">return</span> (
    &lt;span className=<span class="hljs-string">""</span>&gt;
      &lt;motion.span&gt;
          <span class="hljs-comment">// Our text goes here </span>
      &lt;/motion.span&gt;
      &lt;CursorBlinker /&gt; <span class="hljs-comment">// As text grows, the cursor will be "pushed" along</span>
    &lt;/span&gt;
  );
}
</code></pre>
<p>Adding our text into a <code>&lt;span&gt;</code> element will allow our cursor to sit next to it because they are both <code>inline</code>.</p>
<p>To fully use framer motion, we want to use <code>useMotionValue()</code> and not <code>useState()</code>. Motion values use Refs under the hood and don't re-render the entire component every time they change. Motion values can run faster and give us more control by exposing easing functions, delays, loops, etc.</p>
<h3 id="heading-animation">Animation</h3>
<p>Now, we can add the animation. Because we want to animate <code>count</code> only once when the component mounts, we can use <code>useEffect()</code> with an empty dependency array.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> baseText = <span class="hljs-string">"Dear Hiring Manager, "</span> <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">const</span> count = useMotionValue(<span class="hljs-number">0</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> controls = animate(count, baseText.length, {
      <span class="hljs-keyword">type</span>: <span class="hljs-string">"tween"</span>, <span class="hljs-comment">// Not really needed because adding a duration will force "tween"</span>
      duration: <span class="hljs-number">1</span>,
      ease: <span class="hljs-string">"easeInOut"</span>,
    });
    <span class="hljs-keyword">return</span> controls.stop;
  }, []);
</code></pre>
<p>Inside <code>useEffect()</code>, we call framer motion's <code>animate()</code> function that allows us to imperatively animate a single value. It works like this:</p>
<p><code>animate(variableToAnimate, variableTargetValue, transitionOptions)</code></p>
<p>We want to animate <code>count</code> from <code>0</code> (set at initialisation) to <code>baseText.length</code>. I also added a transition to control how the text will appear. I'm going with a simple "tween" animation and an "easeInOut" easing function for our purposes.</p>
<blockquote>
<p>"If you want to get fancy, you could adjust the duration of the animation based on the length of the text, but I'll keep it simple and use one second as a starting point."</p>
</blockquote>
<p><code>return controls.stop</code> cleans up the animation when the component is unmounted. Don't worry about that too much.</p>
<p>Great, now when our component mounts, <code>count</code> is animated from <code>0</code> to <code>baseText.length</code>.</p>
<h3 id="heading-connecting-the-dots">Connecting the dots</h3>
<p>It's time to connect the <code>baseText</code> to <code>count</code>. For that, we need <code>useTransform()</code>.</p>
<p><code>useTransform()</code> can take a motion value as an argument and returns a new, changed motion value. If you haven't used it before, think of <code>useTransform()</code> as "computed values" or linking two values together. When one changes, the other changes, too. There are two ways we can control how the new motion value changes:</p>
<ol>
<li>By adding an input range and an output range:</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> newValue = useTransform(count, [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>], [<span class="hljs-number">0</span>, <span class="hljs-number">100</span>])
<span class="hljs-comment">// When `count` goes from `0` to `1`, I want `newValue` to go from `0` to `100`.</span>
</code></pre>
<ol>
<li>By defining a function that runs every time the input changes:</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> newValue = useTransform(count, <span class="hljs-function">(<span class="hljs-params">latest</span>) =&gt;</span> {latest * <span class="hljs-number">100</span>})
<span class="hljs-comment">// When `count` changes, I want `newValue` to be 100 times that of `count`.</span>
</code></pre>
<p>We will use the second method like so:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> rounded = useTransform(count, <span class="hljs-function">(<span class="hljs-params">latest</span>) =&gt;</span> <span class="hljs-built_in">Math</span>.round(latest));
  <span class="hljs-keyword">const</span> displayText = useTransform(rounded, <span class="hljs-function">(<span class="hljs-params">latest</span>) =&gt;</span>
    baseText.slice(<span class="hljs-number">0</span>, latest)
  );
</code></pre>
<p>We create the <code>rounded</code> motion value, simply an integer version of our <code>count</code>. We do this because we can only slice a string by discrete values. Typing half a letter also doesn't make much sense 🤔.</p>
<p>The second motion value we create is the text we will display on the page. It uses the <code>rounded</code> variable to slice our <code>baseText</code> into a substring. This part links our animated <code>count</code> variable to the text we want to show.</p>
<p>And that's it! We've animated some text! Here's everything together:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { motion, useMotionValue, useTransform, animate } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> CursorBlinker <span class="hljs-keyword">from</span> <span class="hljs-string">"./CursorBlinker"</span>;


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TextAnim</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> baseText = <span class="hljs-string">"Dear Hiring Manager, "</span> <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">const</span> count = useMotionValue(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> rounded = useTransform(count, <span class="hljs-function">(<span class="hljs-params">latest</span>) =&gt;</span> <span class="hljs-built_in">Math</span>.round(latest));
  <span class="hljs-keyword">const</span> displayText = useTransform(rounded, <span class="hljs-function">(<span class="hljs-params">latest</span>) =&gt;</span>
    baseText.slice(<span class="hljs-number">0</span>, latest)
  );

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> controls = animate(count, baseText.length, {
      <span class="hljs-keyword">type</span>: <span class="hljs-string">"tween"</span>,
      duration: <span class="hljs-number">1</span>,
      ease: <span class="hljs-string">"easeInOut"</span>,
    });
    <span class="hljs-keyword">return</span> controls.stop;
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;span className=<span class="hljs-string">""</span>&gt;
      &lt;motion.span&gt;{displayText}&lt;/motion.span&gt;
      &lt;CursorBlinker /&gt;
    &lt;/span&gt;
  );
}
</code></pre>
<h2 id="heading-infinite-typing-animation">Infinite Typing Animation</h2>
<p>Okay, it's time for the cringe sentences. We know how to reveal the text, but what if we want to delete the text? And what about typing something different every time?</p>
<p>Here's the gist of it:</p>
<ol>
<li><p>We reverse the animation so that <code>count</code> goes from <code>0</code> to <code>1</code> and then back to <code>0</code>.</p>
</li>
<li><p>We repeat this indefinitely.</p>
</li>
<li><p>We change the text every time the count hits <code>0</code>.</p>
</li>
</ol>
<p>You're all grown up now, armed to the teeth with animation knowledge, so here's the entire code for this component. We'll dissect them together below.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { motion, useMotionValue, useTransform, animate } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;
<span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RedoAnimText</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> textIndex = useMotionValue(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> texts = [
    <span class="hljs-string">"I am writing to you because I want a job."</span>,
    <span class="hljs-string">"I am the best candidate for this job."</span>,
    <span class="hljs-string">"In my grand adventure as a seasoned designer..."</span>,
    <span class="hljs-string">"Knock knock! Who's there? Your new employee!"</span>,
    <span class="hljs-string">"Walking the tightrope balance of project management..."</span>,
    <span class="hljs-string">"I find myself compelled to express my interest due to..."</span>,
    <span class="hljs-string">"My pen (or should I say, keyboard) is at work today because..."</span>,
    <span class="hljs-string">"Inspired by the alluring challenge in the job posting, I am writing..."</span>,
    <span class="hljs-string">"Stirred to my keyboard by the tantalising nature of the role…"</span>
  ];

  <span class="hljs-keyword">const</span> baseText = useTransform(textIndex, <span class="hljs-function">(<span class="hljs-params">latest</span>) =&gt;</span> texts[latest] || <span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> count = useMotionValue(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> rounded = useTransform(count, <span class="hljs-function">(<span class="hljs-params">latest</span>) =&gt;</span> <span class="hljs-built_in">Math</span>.round(latest));
  <span class="hljs-keyword">const</span> displayText = useTransform(rounded, <span class="hljs-function">(<span class="hljs-params">latest</span>) =&gt;</span>
    baseText.get().slice(<span class="hljs-number">0</span>, latest)
  );
  <span class="hljs-keyword">const</span> updatedThisRound = useMotionValue(<span class="hljs-literal">true</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    animate(count, <span class="hljs-number">60</span>, {
      <span class="hljs-keyword">type</span>: <span class="hljs-string">"tween"</span>,
      duration: <span class="hljs-number">1</span>,
      ease: <span class="hljs-string">"easeIn"</span>,
      repeat: <span class="hljs-literal">Infinity</span>,
      repeatType: <span class="hljs-string">"reverse"</span>,
      repeatDelay: <span class="hljs-number">1</span>,
      onUpdate(latest) {
        <span class="hljs-keyword">if</span> (updatedThisRound.get() === <span class="hljs-literal">true</span> &amp;&amp; latest &gt; <span class="hljs-number">0</span>) {
          updatedThisRound.set(<span class="hljs-literal">false</span>);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (updatedThisRound.get() === <span class="hljs-literal">false</span> &amp;&amp; latest === <span class="hljs-number">0</span>) {
          <span class="hljs-keyword">if</span> (textIndex.get() === texts.length - <span class="hljs-number">1</span>) {
            textIndex.set(<span class="hljs-number">0</span>);
          } <span class="hljs-keyword">else</span> {
            textIndex.set(textIndex.get() + <span class="hljs-number">1</span>);
          }
          updatedThisRound.set(<span class="hljs-literal">true</span>);
        }
      }
    });
    <span class="hljs-comment">// eslint-disable-next-line react-hooks/exhaustive-deps</span>
  }, []);

  <span class="hljs-keyword">return</span> &lt;motion.span className=<span class="hljs-string">"inline"</span>&gt;{displayText}&lt;/motion.span&gt;;
}
</code></pre>
<h3 id="heading-variables">Variables</h3>
<p>First of all, we have some new variables:</p>
<ul>
<li><p><code>texts</code> holds all our texts.</p>
</li>
<li><p><code>textIndex</code> keeps track of which text we're currently on.</p>
</li>
<li><p><code>baseText</code> is now a motion value derived from the <code>text</code> and <code>textIndex</code>.</p>
</li>
<li><p><code>updatedThisRound</code> helps us change the text only once per loop.</p>
</li>
</ul>
<h3 id="heading-animation-1">Animation</h3>
<p>The animation is almost the same as the first, but with a few key differences:</p>
<ul>
<li><p>I use <code>60</code> as a hardcoded length because a dynamic value won't change between repeats.</p>
</li>
<li><p>I added <code>repeat: Infinity</code> for an endless loop.</p>
</li>
<li><p><code>repeatType: "reverse"</code> makes the animation go backwards instead of starting from the beginning. This simulates the person hitting "backspace".</p>
</li>
<li><p><code>repeatDelay: 1</code> gives the user a second to read the complete sentence before being deleted.</p>
</li>
</ul>
<h3 id="heading-swapping-the-text">Swapping the Text</h3>
<p>The tricky part is swapping the text when <code>count</code> is <code>0</code>. Transition objects have some handy triggers that allow us to call a function during certain stages of our animation. We can use those to increment <code>textIndex</code> to swap out our current text.</p>
<p><code>onComplete()</code> gets triggered at the end of our animation, but since our animation never ends, it never gets called.</p>
<p>We need a trigger that gets called every repeat. There is no <code>onRepeat()</code> in framer motion, so we must build it ourselves.</p>
<p><code>onUpdate()</code> lets us call a function on every frame. But simply checking if <code>latest === 0</code> and incrementing <code>textIndex</code> won't work because <code>onUpdate()</code> is called sixty times per second (or whatever your browser's frame rate is). Since we're waiting at <code>count = 0</code> for 1 second, we would increment <code>textIndex</code> sixty times!</p>
<p>That's where <code>updatedThisRound</code> comes in. It acts as a latch, allowing us to increment <code>textIndex</code> only once per repeat. It has to be a motion value because we don't want our component to re-render when its value changes. Here's how it works:</p>
<pre><code class="lang-typescript">onUpdate(latest) {
    <span class="hljs-comment">// If we updated already and we're not at 0 anymore, </span>
    <span class="hljs-comment">// set updatedThisRound to false.</span>
    <span class="hljs-comment">// The next time we hit 0, we will increment.</span>
    <span class="hljs-keyword">if</span> (updatedThisRound.get() === <span class="hljs-literal">true</span> &amp;&amp; latest &gt; <span class="hljs-number">0</span>) {
      updatedThisRound.set(<span class="hljs-literal">false</span>);

    <span class="hljs-comment">// If we haven't updated yet and we're at 0,</span>
    <span class="hljs-comment">// increment and set updatedThisRound to true.</span>
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (updatedThisRound.get() === <span class="hljs-literal">false</span> &amp;&amp; latest === <span class="hljs-number">0</span>) {

    <span class="hljs-comment">// Set textIndex to 0 if we reach the end of our texts array.</span>
    <span class="hljs-comment">// So we don't run out of silly sentences</span>
      <span class="hljs-keyword">if</span> (textIndex.get() === texts.length - <span class="hljs-number">1</span>) {
        textIndex.set(<span class="hljs-number">0</span>);
      } <span class="hljs-keyword">else</span> {
        textIndex.set(textIndex.get() + <span class="hljs-number">1</span>);
      }
      updatedThisRound.set(<span class="hljs-literal">true</span>);
    }
  }
</code></pre>
<p>And yes, running a bunch of conditionals 60 times per second sends me straight to performance hell, but hey... I want silly sentences. And besides, there's only a little else on my homepage, so I figured it was okay. Let me know if you have a better solution :)</p>
<h2 id="heading-the-layout">The Layout</h2>
<p>Here, we keep it simple. We need a container that looks like a Google Doc. I decided to take a couple of elements like the logo, the document title ("untitled document", of course) and the formatting toolbar buttons.</p>
<p>I used an aspect ratio of 1/1.41, the same as A4. The rest is just CSS.</p>
<p>I added a short enter animation to the layout. The child components come in slightly later with <code>delayChildren</code> and <code>staggerChildren</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { DocumentTextIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/20/solid"</span>;
<span class="hljs-keyword">import</span> { motion } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;
<span class="hljs-keyword">import</span> AnimText <span class="hljs-keyword">from</span> <span class="hljs-string">"./AnimText"</span>;

<span class="hljs-keyword">const</span> containerVariants = {
  hidden: {
    opacity: <span class="hljs-number">0</span>,
    y: <span class="hljs-number">30</span>
  },
  visible: {
    opacity: <span class="hljs-number">1</span>,
    y: <span class="hljs-number">0</span>,
    transition: {
      duration: <span class="hljs-number">0.3</span>,
      ease: <span class="hljs-string">"easeOut"</span>,
      delayChildren: <span class="hljs-number">0.3</span>,
      staggerChildren: <span class="hljs-number">0.1</span>
    }
  }
};

<span class="hljs-keyword">const</span> itemVariants = {
  hidden: {
    opacity: <span class="hljs-number">0</span>,
    y: <span class="hljs-number">15</span>
  },
  visible: {
    opacity: <span class="hljs-number">1</span>,
    y: <span class="hljs-number">0</span>,
    transition: {
      duration: <span class="hljs-number">0.3</span>,
      ease: <span class="hljs-string">"easeOut"</span>
    }
  }
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">A4Animation</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;motion.div className=<span class="hljs-string">"flex w-full select-none items-center justify-center "</span>&gt;
      &lt;motion.div
        variants={containerVariants}
        animate=<span class="hljs-string">"visible"</span>
        initial=<span class="hljs-string">"hidden"</span>
        className=<span class="hljs-string">"flex aspect-[1/1.41] h-[500px] flex-col rounded-2xl bg-white p-2"</span>
      &gt;
        &lt;motion.div
          variants={itemVariants}
          className=<span class="hljs-string">"flex items-center space-x-2"</span>
        &gt;
          &lt;DocumentTextIcon className=<span class="hljs-string">"h-8 w-8 text-indigo-700"</span> /&gt;
          &lt;span className=<span class="hljs-string">"text-slate-700"</span>&gt;Untitled <span class="hljs-built_in">document</span>&lt;/span&gt;
        &lt;/motion.div&gt;
        &lt;motion.div
          variants={itemVariants}
          className=<span class="hljs-string">"flex items-center justify-center"</span>
        &gt;
          &lt;div className=<span class="hljs-string">"mt-2 flex space-x-4 rounded-full bg-slate-100 px-8 text-slate-700"</span>&gt;
            &lt;strong&gt;B&lt;/strong&gt;
            &lt;span className=<span class="hljs-string">"font-italic"</span>&gt;I&lt;/span&gt;
            &lt;span className=<span class="hljs-string">"underline"</span>&gt;U&lt;/span&gt;
            &lt;strong className=<span class="hljs-string">"underline"</span>&gt;A&lt;/strong&gt;
          &lt;/div&gt;
        &lt;/motion.div&gt;
        &lt;motion.span
          variants={itemVariants}
          className=<span class="hljs-string">"inline h-full w-full p-8 text-lg text-slate-900"</span>
        &gt;
          &lt;AnimText /&gt;
        &lt;/motion.span&gt;
      &lt;/motion.div&gt;
    &lt;/motion.div&gt;
  );
}
</code></pre>
<h2 id="heading-finishing-touches">Finishing Touches</h2>
<p>Below is the CodeSandbox of the entire effect. I added some line breaks in the final version when the first animation ends. There is also a delay to make sure one happens after the other. Check it out on your own time to see how it works. Leave me a comment if you need any help.</p>
<iframe src="https://codesandbox.io/embed/text-typing-animation-rtzvgl?fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2Fcomponents%2FA4Layout.tsx&amp;theme=dark&amp;view=editor" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<h2 id="heading-conclusion">Conclusion</h2>
<p>Animation complete! Congratulations! 🍾</p>
<p>Thank you for reading. I hope you learned something. As you know, I just launched <a target="_blank" href="https://www.covercraft.io/">CoverCraft</a>, and I would be exceedingly happy if you tried it out. It's still early days, but any feedback is welcome. I am thinking about writing a series on how I built CoverCraft. Let me know if you would be interested in something like that.</p>
<p>Have a great day :)</p>
]]></content:encoded></item><item><title><![CDATA[Maximizing the Power of Framer Motion with useAnimationControls]]></title><description><![CDATA[For most animations, we create variants for different animation states and plug those into the animate prop of our motion component. animate controls which variant the component animates to. It is triggered automatically when the component loads or t...]]></description><link>https://blog.noelcserepy.com/maximizing-the-power-of-framer-motion-with-useanimationcontrols</link><guid isPermaLink="true">https://blog.noelcserepy.com/maximizing-the-power-of-framer-motion-with-useanimationcontrols</guid><category><![CDATA[framer-motion]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[CSS Animation]]></category><category><![CDATA[CSS]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Noël Cserépy]]></dc:creator><pubDate>Mon, 09 Jan 2023 11:56:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673265094309/bb1c4e31-6b30-4f0b-95f9-a1fffa902727.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For most animations, we create <em>variants</em> for different animation states and plug those into the <code>animate</code> prop of our motion component. <code>animate</code> controls which variant the component animates to. It is triggered automatically when the component loads or the <code>animate</code> prop changes. We can use conditional logic or state to change the variant in <code>animate</code>.</p>
<p>This is great, but what if we wanted to chain those animations and move from variant to variant? What if we're going to time precisely when these animations happen? What if we want to compose animations on different components?</p>
<p>For that, we need more...</p>
<p><img src="https://media3.giphy.com/media/xPGkOAdiIO3Is/giphy.gif" alt="csi" /></p>
<p><strong>CONTROL</strong>.</p>
<h2 id="heading-why-use-animation-controls">Why use animation controls?</h2>
<p>We discussed how to create animation sequences using keyframes in the <a target="_blank" href="https://blog.noelcserepy.com/creating-keyframe-animations-with-framer-motion">previous article</a>. However, there were several drawbacks to keyframes, namely:</p>
<ul>
<li><p>Adding pauses is painful</p>
</li>
<li><p>Composition drives complexity</p>
</li>
<li><p>Useless keyframes</p>
</li>
</ul>
<p>Animation controls solve these problems and open new ways to integrate our animations into a living and breathing app. Let's jump right in!</p>
<h3 id="heading-useanimationcontrols">useAnimationControls()</h3>
<p>Framer motion exposes a hook called <code>useAnimationControls()</code>. It returns an object we can pass to our component's <code>animate</code> prop. Using this <em>controls</em> object, we can manually start, stop and schedule animations. Let's set up our component and see what it can do.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { motion, useAnimationControls } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;
<span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> divVariants = {
  <span class="hljs-attr">left</span>: {
    <span class="hljs-attr">x</span>: <span class="hljs-number">-10</span>,
    <span class="hljs-attr">transition</span>: {
      <span class="hljs-attr">duration</span>: <span class="hljs-number">1</span>
    }
  },
  <span class="hljs-attr">right</span>: {
    <span class="hljs-attr">x</span>: <span class="hljs-number">10</span>,
    <span class="hljs-attr">transition</span>: {
      <span class="hljs-attr">duration</span>: <span class="hljs-number">1</span>
    }
  }
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Loader</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> divControls = useAnimationControls();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Using properties</span>
    divControls.start({
      <span class="hljs-attr">x</span>: <span class="hljs-number">20</span>,
      <span class="hljs-attr">transition</span>: {
        <span class="hljs-attr">duration</span>: <span class="hljs-number">2</span>
      }
    });

    <span class="hljs-comment">// Using variants</span>
    divControls.start(<span class="hljs-string">"left"</span>)
    divControls.set(<span class="hljs-string">"right"</span>);
    divControls.stop();
  }, []);

  <span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.div</span> 
  <span class="hljs-attr">variants</span>=<span class="hljs-string">{divVariants}</span>
  <span class="hljs-attr">animate</span>=<span class="hljs-string">{divControls}</span> /&gt;</span></span>
  )
}
</code></pre>
<p>In the code above, we import the <code>useAnimationControls</code> hook, create variants and define our Loader component.</p>
<p>In the loader component we call the hook to get a controls object. We add the controls to our motion component's <code>animate</code> prop and our variants to the <code>variants</code> prop.</p>
<h2 id="heading-control-functions">Control functions</h2>
<p>The controls object has 3 methods we can use:</p>
<ul>
<li><p><strong>start</strong>: triggers the animation. We can provide an animation target directly using properties or the name of the variant we wish to animate to.</p>
</li>
<li><p><strong>set</strong>: instantly sets the new values without animating. (works the same as duration: 0)</p>
</li>
<li><p><strong>stop</strong>: stops the currently running animation</p>
</li>
</ul>
<p>These three functions allow us to trigger our animations imperatively. We can start and stop animations from an event handler or tie them into our component's lifecycle using <code>useEffect()</code>. This also has some benefits we will get to in the next section.</p>
<p>If you are unfamiliar with <code>useEffect()</code>, check out this excellent <a target="_blank" href="https://youtu.be/dH6i3GurZW8">Jack Herrington tutorial</a>. He is the Bob Ross for React, and I can't recommend his videos highly enough.</p>
<h2 id="heading-imperative-animations">Imperative animations</h2>
<p>It can be beneficial to have imperative animations at your fingertips. For instance, when the user presses "like", you might want to animate a heart icon. We could add a click handler function and use controls to trigger our animation.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// Heart component on a blog article</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Heart</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> divControls = useAnimationControls();

    <span class="hljs-keyword">const</span> handleClick = <span class="hljs-function">() =&gt;</span> {
        divControls.start({
        <span class="hljs-attr">scale</span>: [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">1</span>], <span class="hljs-comment">// Using Keyframes to grow and shrink the heart</span>
        <span class="hljs-attr">transition</span>: {
            <span class="hljs-attr">duration</span>: <span class="hljs-number">0.5</span>
        }
    });
    }

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.div</span> 
        <span class="hljs-attr">animate</span>=<span class="hljs-string">{divControls}</span> 
        <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleClick}</span> /&gt;</span></span>
    )
}
</code></pre>
<p>This can be useful for fine-grained control over a component's animations. Using conditional logic within the click handler allows us to trigger different animations based on various conditions of our component.</p>
<h2 id="heading-lifecycle-animations">Lifecycle animations</h2>
<p>Complete manual control is sometimes handy, but what about linking the animations directly to the component's state? This can easily be achieved with the <code>useEffect()</code> hook.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// Heart component on a blog article</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Heart</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [isLiked, setIsLiked] = useState(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> divControls = useAnimationControls();

    useEffect(<span class="hljs-function">() =&gt;</span> {
        divControls.start({
            <span class="hljs-attr">scale</span>: [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">1</span>], <span class="hljs-comment">// Using Keyframes to grow and shrink the heart</span>
            <span class="hljs-attr">transition</span>: {
                <span class="hljs-attr">duration</span>: <span class="hljs-number">0.5</span>
            }
        });
    }, [isLiked]); <span class="hljs-comment">// Depending on isLiked state</span>

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.div</span> 
        <span class="hljs-attr">animate</span>=<span class="hljs-string">{divControls}</span> /&gt;</span></span>
    )
}
</code></pre>
<p>By adding the <code>isLiked</code> variable to the dependency array, React will re-run <code>useEffect()</code> every time the variable changes. We can use this to trigger the animation automatically every time the user likes or un-likes.</p>
<p>What's great about this is that the animation does not rely on a click event but is directly tied to the component state. That means changing the <code>isLiked</code> state of our heart component from somewhere else will also trigger the animation. For example, if you have multiple items selected and wish to like them all, simply changing their state will animate all hearts simultaneously. Extremely useful stuff!</p>
<h2 id="heading-control-multiple-components">Control multiple components</h2>
<p>The same control object can be used on multiple different motion components. Plug in the control object to the <code>animate</code> prop like so:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">motion.div</span> 
        <span class="hljs-attr">variants</span>=<span class="hljs-string">{divVariants}</span>
        <span class="hljs-attr">animate</span>=<span class="hljs-string">{commonControls}</span> 
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">motion.p</span> 
        <span class="hljs-attr">variants</span>=<span class="hljs-string">{pVariants}</span>
        <span class="hljs-attr">animate</span>=<span class="hljs-string">{commonControls}</span> 
        /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
    )
}
</code></pre>
<p>This will make all components controllable from a single controls object!</p>
<p>Note that <strong>each component will use its own variants</strong>. Calling <code>commonControls.start("enter")</code> will try to animate both components to their respective "enter" variant. So if you use the same controls on multiple components, make sure to either use properties to animate or use the same variant names on both components.</p>
<p>The <code>animate</code> prop is inherited if you do not define an <code>animate</code> prop on child components. This also applies to controls. That means you could add variants to your child's components and control them with the parent's controls object. This way, you could control the animations of a whole component tree in one go. Potent stuff!</p>
<p>If you do not want this behaviour, you can set the child's <code>animate</code> prop to any other value.</p>
<h2 id="heading-sequences">Sequences</h2>
<p>We can manually control when the animations run, but how do we chain them?</p>
<pre><code class="lang-jsx">useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> sequence = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> divControls.start(<span class="hljs-string">"left"</span>};
        divControls.start(<span class="hljs-string">"right"</span>};
    }

    sequence();
});
</code></pre>
<p>To create a sequence, we create an async function within <code>useEffect()</code> and call it. This will run all of our control functions. But that's not all. The tasks on our control variable return a promise. Making the function asynchronous allows us to await the completion of the previous animation before we start the new one. In the example above, the "right" animation will only play after the "left" animation has finished.</p>
<p>Remember, we are still setting the <code>animate</code> prop for a single motion component. That single component can only perform one animation at a time. To animate multiple properties of that single component, you must create variants to reflect that.</p>
<h2 id="heading-sequences-within-sequences">Sequences within sequences</h2>
<p>Consider the following code:</p>
<pre><code class="lang-jsx">useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> sequence1 = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> divControls.start(<span class="hljs-string">"left"</span>};
        <span class="hljs-keyword">await</span> divControls.start(<span class="hljs-string">"right"</span>};
    }
    <span class="hljs-keyword">const</span> sequence2 = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> divControls.start(<span class="hljs-string">"up"</span>};
        <span class="hljs-keyword">await</span> divControls.start(<span class="hljs-string">"down"</span>};
    }
    <span class="hljs-keyword">const</span> sequenceJoined = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> sequence1();
        <span class="hljs-keyword">await</span> sequence2();
    }

    sequenceJoined();
});
</code></pre>
<p><code>sequenceJoined</code> contains a sequence that first runs <code>sequence1</code> and then runs <code>sequence2</code>. This means that when <code>sequenceJoined</code> is called, the motion component will first animate to the "left" variant, then to the "right" variant, then to the "up" variant, and finally to the "down" variant.</p>
<p>This demonstrates how you can nest sequences within sequences to create complex animation chains. This technique allows for more composability in designing your animations while retaining complete control over the timing and order.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Before your head starts spinning with all the possibilities, let's call it a day.</p>
<p>To sum it up, <code>useAnimationControls()</code> is extremely useful when you need to take things into your own hands. You can define the behaviour of your animations exactly how you want by creating sequences or tying animations into the component lifecycle.</p>
<p>Thank you for reading! Feel free to ask me any questions.</p>
]]></content:encoded></item><item><title><![CDATA[Creating Keyframe Animations with Framer Motion]]></title><description><![CDATA[Sometimes a single movement is simply not enough. Sometimes you want to emphasize an action, draw attention, or even build complex animations with multiple components. If you want to really sell the effect, an animation sequence is just what you need...]]></description><link>https://blog.noelcserepy.com/creating-keyframe-animations-with-framer-motion</link><guid isPermaLink="true">https://blog.noelcserepy.com/creating-keyframe-animations-with-framer-motion</guid><category><![CDATA[framer-motion]]></category><category><![CDATA[React]]></category><category><![CDATA[animation]]></category><category><![CDATA[4articles4weeks]]></category><category><![CDATA[keyframes]]></category><dc:creator><![CDATA[Noël Cserépy]]></dc:creator><pubDate>Mon, 03 Oct 2022 16:19:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664813781447/xTbkDufwb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometimes a single movement is simply not enough. Sometimes you want to emphasize an action, draw attention, or even build complex animations with multiple components. If you want to really sell the effect, an animation sequence is just what you need. </p>
<p>Simple animations might go <strong><em>shwoop</em></strong> 💨</p>
<p>But with sequences, they can go <strong><em>shwoop BAM!</em></strong> 💨💥</p>
<p>And who doesn't want that? Am I right?</p>
<h2 id="heading-see-for-yourself">See for yourself</h2>
<p>Below is a button to open and close a menu. This simple action can feel a lot smoother if we use a sequence. Click on the orange "open" text in the CodeSandbox below to see what I mean.</p>
<iframe src="https://codesandbox.io/embed/keyframes-intro-1xz7ip?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<p>Our simple menu button became a joy with just a few lines of code. Feel free to look at the code by swiping the handle on the left. By the end of this article, you will understand precisely how it all works. </p>
<h2 id="heading-lets-get-started">Let's get started!</h2>
<p>In Framer Motion, there are several ways to create such animation sequences. All methods work differently, and each method has its pros and cons. Today we will dive into the first method: <strong>keyframes</strong>. </p>
<p>I will be using <a target="_blank" href="https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion#heading-variants">variants</a>, <a target="_blank" href="https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion#heading-transitions">transitions</a>, <a target="_blank" href="https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion#heading-transitions">looping</a> and <a target="_blank" href="https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion#heading-transitions">easing functions</a> liberally in this article. Click the links if you don't know what they are or need a quick refresher. They will take you to my <a target="_blank" href="https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion">previous article</a>, where I explain those concepts in more detail. </p>
<h2 id="heading-what-are-keyframes">What are keyframes?</h2>
<p>This concept will be familiar if you are acquainted with animation or video editing. Keyframes are used to define the most essential or most extreme poses and positions in an animation. Using keyframes as start and end points, animators would add additional <em>in-between</em> frames to create smooth transitions. </p>
<p>Framer Motion also allows us to use keyframes to define an animation sequence. They are a fantastic tool for creating looping animations and short movements. They can be integrated into other sequences and have a clever trick up their sleeve for switching between animations.</p>
<h2 id="heading-using-keyframes">Using keyframes</h2>
<p>Typically when we use the <code>animate</code> prop, we provide a single value for the property we wish to animate, e.g. <code>x: 10</code>. This will act as the destination. </p>
<p>To create a keyframe sequence, we can instead provide an <strong>array of values</strong>.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { motion } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;

<span class="hljs-keyword">const</span> variants = {
  <span class="hljs-attr">slide</span>: {
    <span class="hljs-attr">x</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">200</span>, <span class="hljs-number">0</span>]
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Loader</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.div</span> 
  <span class="hljs-attr">variants</span>=<span class="hljs-string">{variants}</span>
  <span class="hljs-attr">animate</span>=<span class="hljs-string">"slide"</span> /&gt;</span></span>
  )
}
</code></pre>
<p>Framer Motion will automatically detect that we are using keyframes and animate our <code>x</code> value from <code>0</code> to <code>200</code> and back to <code>0</code>. This method works without specifying anything else because of the convenient default values Framer Motion uses. </p>
<h2 id="heading-basic-examples">Basic examples</h2>
<p>Luckily, we don't need to draw any <em>in-between</em> frames by hand to create smooth transitions. Framer Motion handles that for us. Let's look at some examples and see how we can use the <code>transition</code> options for different effects.</p>
<iframe src="https://codesandbox.io/embed/keyframes-l6regq?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<h3 id="heading-default">Default</h3>
<p>Only the keyframes array and <code>duration</code> has been set here. The keyframes are spread evenly over the total duration. Each transition between keyframes takes the same amount of time to complete. </p>
<pre><code class="lang-jsx">transition: {
    <span class="hljs-attr">duration</span>: <span class="hljs-number">1</span>
}
</code></pre>
<h3 id="heading-times">Times</h3>
<p>If we want more control over the transition timing, we can use the <code>times</code> option. <code>times</code> is an array of numbers ranging from <code>0</code> to <code>1</code>. One number for every keyframe. Each number defines when the corresponding key frame should be reached in the animation. </p>
<blockquote>
<p>Important: The length of the <code>times</code> array has to be equal to the length of the keyframe array.</p>
</blockquote>
<p>In the example, the component starts at <code>x: 0</code>. It reaches <code>x: 200</code> after 0.8 seconds and then returns to <code>x: 0</code> by 1 second. If we double our <code>duration</code> to 2 seconds, these times would also double.</p>
<pre><code class="lang-jsx">transition: {
    <span class="hljs-attr">times</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">0.8</span>, <span class="hljs-number">1</span>]
}
</code></pre>
<h3 id="heading-ease">Ease</h3>
<p>Defining a single value such as <code>ease: "easeIn"</code> will apply that easing function to all transitions between keyframes. Not setting an <code>ease</code> value will default to <code>ease: "easeInOut"</code>.  </p>
<p>If we add an array of easing functions, we can assign a different easing function to every transition. Note that the length of our easing function array must equal the number of <strong>transitions</strong>. That means the number of keyframes minus one. </p>
<pre><code class="lang-jsx">transition: {
    <span class="hljs-attr">ease</span>: [<span class="hljs-string">"easeIn"</span>, <span class="hljs-string">"easeOut"</span>]
}
</code></pre>
<h3 id="heading-null">Null</h3>
<p>In all of our previous examples, triggering the animation forces <code>x</code> to snap to the first keyframe, regardless of its current position. That means if we begin a new animation while the component is already moving, it snaps instantly to the starting position of the new animation. This can look jarring in certain conditions. </p>
<p>When we set the first keyframe to <code>null</code>, it will act as a wildcard value. <strong><code>null</code> will be replaced by the component's current value</strong> when the animation starts. This lets us transition smoothly between animations. This technique is convenient when we implement drag animations or other gestures.</p>
<pre><code class="lang-jsx">transition: {
    <span class="hljs-attr">x</span>: [<span class="hljs-literal">null</span>, <span class="hljs-number">200</span>, <span class="hljs-number">0</span>]
}
</code></pre>
<h2 id="heading-advanced-examples">Advanced examples</h2>
<iframe src="https://codesandbox.io/embed/loader-dji5j4?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<h3 id="heading-jumping-ball">Jumping ball</h3>
<p>I like this example because it shows us a clever and concise way of using keyframes.</p>
<p>The ball jumps up and down using only <code>y: [0, -70]</code>. The ball looks like it is being affected by gravity because we apply <code>ease: "easeOut"</code>. This will slow down the ball on the way up. But that is only half of the animation. What about the rest?</p>
<p>Now comes the clever bit. We are repeating this animation with <code>repeatType: "reverse"</code>. This will play the animation backwards. The ball slows down on the way up, reaches the apex and then speeds up on the way down. This also demonstrates what the different values for <code>repeatType</code> do. If we had used <code>"mirror"</code>, the ball would descend quickly at first and slow down before it reached the ground. </p>
<p>This animation demonstrates another aspect of keyframes. Namely, we can store a whole animation sequence within one variant. You can switch between two animation sequences if you click on the button. This is useful when we want to pack interesting interactions into a small footprint.</p>
<p>You can fork the CodeSandbox and try changing the <code>ease</code> and <code>repeatType</code> options to better understand this. </p>
<h3 id="heading-tumbler">Tumbler</h3>
<p>In the second example, I use almost all of the abovementioned techniques. My goal here was to make a domino that keeps falling forward. It is a simple loading animation, yet it shows some of the most significant limitations when using keyframes. </p>
<p>The animation starts with the box standing tall in the middle of the platform. I set <code>originX: 1</code> and <code>originY: 1</code> to move our <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin">transform origin</a> to the bottom right. This allowed me to rotate the box around that corner to mimic falling. </p>
<p>Because the box is 80px tall and 20px wide, tipping it to the side twice will move it by 100px. Thus, I use <code>x: -100</code> to slide the box to the left to keep it in the same place. </p>
<p>Because the shape tips on its side, the transform-origin is now on the <strong>bottom left</strong>. Yet, to tip the shape upright again, I need to rotate it around the <strong>bottom right</strong>. All the movements I have done thus far have been in relation to my original transform-origin. Moving the transform-origin is problematic because it will change all my previous transformations.</p>
<p>My solution was to <strong>swap the component</strong> for a new one using <code>opacity</code>. The new component starts lying down and has its origin on the bottom right. I can then tip the new component to its vertical position and swap it back to complete the loop.</p>
<p>After all this, you are probably wondering where the loop even began. Even if you look at the code, you might think, "😨 My goodness! This looks frightfully hacky!"</p>
<p>And you would be right!</p>
<p>Let's talk about it.</p>
<h2 id="heading-drawbacks-of-keyframe-sequences">Drawbacks of keyframe sequences</h2>
<p>Although keyframes can create great-looking sequences, there are some concerns about usability. </p>
<h3 id="heading-useless-keyframes">Useless keyframes</h3>
<p>In many cases, we want to animate multiple properties of our component (e.g. <code>opacity</code> and <code>x</code>). We can simply add the keyframes for both properties. </p>
<pre><code class="lang-jsx">variant: {
    <span class="hljs-attr">opacity</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>],
    <span class="hljs-attr">x</span>: [<span class="hljs-number">100</span>, <span class="hljs-number">0</span>],
}
</code></pre>
<p>But the whole point of a sequence is that things happen one after the other. <strong><em>Shwoop BAM!</em></strong> 💨💥, remember? Meaning, not all properties are animating at the same time. </p>
<p>If we want to animate <code>opacity</code> <strong>before</strong> <code>x</code>, we need to add useless keyframes to every property we are not currently animating.</p>
<pre><code class="lang-jsx">variant: {
    <span class="hljs-attr">opacity</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>],
    <span class="hljs-attr">x</span>: [<span class="hljs-number">100</span>, <span class="hljs-number">100</span>, <span class="hljs-number">0</span>],
}
</code></pre>
<p>This forces us to duplicate keyframes. This isn't a problem for simple animations but adds up quickly when we are animating many properties with many keyframes.</p>
<h3 id="heading-composition-drives-complexity">Composition drives complexity</h3>
<p>Managing multiple components with keyframes is a pain. The reason is <strong>timing</strong>. </p>
<p>The <code>times</code> option uses fractions of the total duration. This is handy if we animate one component, but what about two? Suppose we try to start two animations from different components simultaneously. In that case, we need to calculate the start time by multiplying the total duration by the <code>times</code> value of the given keyframe. Confusing, right?</p>
<p>I altogether avoided that in the <em>Tumbler</em> example by using the same duration for both components and matching the <code>times</code> values. But this brings us back to adding useless keyframes. </p>
<p>And what if we want to change the timing? We need to go into every property array and every <code>times</code> array to update values. Keeping track of things like this turns into a nightmare in no time.</p>
<h3 id="heading-adding-pauses-is-painful">Adding pauses is painful</h3>
<p>To add a pause, you must duplicate all properties' keyframes and then define a new <code>time</code> value. </p>
<pre><code class="lang-jsx">variant: {
    <span class="hljs-attr">x</span>: [<span class="hljs-number">100</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">100</span>],
    <span class="hljs-attr">transition</span>: {
        <span class="hljs-attr">times</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">0.4</span>, <span class="hljs-number">0.6</span>, <span class="hljs-number">1</span>]
    }
}
</code></pre>
<p>Needless to say, this is not ideal, especially if you have many properties. </p>
<h2 id="heading-the-better-way">The better way</h2>
<p>This article covered creating keyframe animations and using the various <code>transition</code> options. With the help of examples, we discussed keyframe animations' uses and limitations. This ended the article on a rather glum note. But what if I told you there was a better way?</p>
<p>Join me in part 2, where we will explore <code>useAnimationControls()</code> and solve some of the issues we have with keyframes. </p>
]]></content:encoded></item><item><title><![CDATA[How to Animate SVG Paths with Framer Motion]]></title><description><![CDATA[If you're looking for some flair when fading in a logo or want to give loading animations some spice, SVG path animations are just the thing! Framer motion makes it easy for you to create great looking path animations for your React project.
Let's se...]]></description><link>https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion</link><guid isPermaLink="true">https://blog.noelcserepy.com/how-to-animate-svg-paths-with-framer-motion</guid><category><![CDATA[framer-motion]]></category><category><![CDATA[React]]></category><category><![CDATA[4articles4weeks]]></category><category><![CDATA[SVG]]></category><category><![CDATA[SVG Animation]]></category><dc:creator><![CDATA[Noël Cserépy]]></dc:creator><pubDate>Tue, 20 Sep 2022 14:30:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663684036168/D6M44D4Bs.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you're looking for some flair when fading in a logo or want to give loading animations some spice, SVG path animations are just the thing! Framer motion makes it easy for you to create great looking path animations for your React project.</p>
<p>Let's see how path animations work!</p>
<h2 id="heading-svg-paths">SVG Paths</h2>
<p>Paths are by far the most powerful SVG element. They can create arcs, curves, lines and more. If you used Photoshop, Illustrator or Inkscape, you are likely already familiar with paths. It is the SVG equivalent of the pen tool.</p>
<p>It draws a line on the canvas, moving from point to point. The <code>d</code> attribute controls where that line goes. It uses commands and parameters passed as a string. The path can move straight or curved. It can move relative to its current position, and it can move in absolute terms. The coordinates in the <code>d</code> parameter are always unitless. That means they are in the user coordinate system established in the parent <code>&lt;svg&gt;</code> component. If you don't know what that is, I wrote a <a target="_blank" href="https://blog.noelcserepy.com/3-concepts-to-master-svgs">blog post with interactive examples</a> that covers this in detail.</p>
<p>I won't go into further detail on this because the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths">MDN docs</a> on paths are genuinely excellent.</p>
<p>Beziér curves are part of what makes paths so potent. If you find them just as fascinating as I do, check out <a target="_blank" href="https://youtu.be/aVwxzDHniEw">Freya Holmér's video on Bézier Curves</a>.</p>
<h2 id="heading-how-path-animations-work">How Path Animations Work</h2>
<p>One can use a clever trick to get the effect of the path being <em>drawn</em>. To animate the path, we need to control two attributes of the <code>&lt;path&gt;</code> element.</p>
<p>The first is <code>stroke-dasharray</code>, which turns our path into a dashed line. We can define a repeating pattern that describes how large the dashes and gaps between them are. Setting a value of <code>"10 10"</code> would represent a dash that is ten units long, followed by a gap of 10 units long. This pattern repeats for the length of the path.</p>
<p>The second is <code>stroke-dashoffset</code>, which pushes our pattern by a given amount along the path.</p>
<p><strong>Now for our trick:</strong> We can make the dashes and gaps as long as the path itself. If we then use <code>stroke-dashoffset</code> to move the pattern, we can completely hide the dash and only show the gap.</p>
<p><code>stroke-dashoffset</code> can then be animated to make the path <em>draw itself</em>. Click on the interactive window below to see for yourself.</p>
<iframe src="https://examples.noelcserepy.com/svg/pathanimation" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden"></iframe>

<p>To accurately hide the path, we usually need to get the total length of the path using something like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">var</span> path = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'.path'</span>);
<span class="hljs-keyword">var</span> length = path.getTotalLength();
</code></pre>
<p>But this is not necessary when we use Framer Motion. Although you can animate all these values, motion components expose unique attributes that allow us to do all of the above with a single value.</p>
<p>Let's get into it!</p>
<h2 id="heading-installing-framer-motion">Installing Framer Motion</h2>
<p>Navigate to the root directory of your React project (React 18 or greater). Run the following command.</p>
<pre><code class="lang-shell">npm install framer-motion
</code></pre>
<p>After installation, import Framer Motion wherever you want to use it.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { motion } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>
</code></pre>
<p>Check out the <a target="_blank" href="https://www.framer.com/docs/introduction/##installation">installation docs</a> for more information.</p>
<h2 id="heading-convert-to-a-motion-component">Convert <code>&lt;path&gt;</code> to a Motion Component</h2>
<p>Framer Motion gives us some valuable shortcuts for animating SVG paths. We need to convert our existing components into <em>motion components</em>to use them. We can do this by importing <code>motion</code> at the top of the document and adding <code>motion.</code> to the HTML element we want to animate. Don't forget to add <code>motion.</code> to the closing tags.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { motion } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.svg</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">motion.path</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">motion.svg</span>&gt;</span></span>
</code></pre>
<p>Now that we have motion components, we can start animating!</p>
<h2 id="heading-animating-the-motion-component">Animating the motion component</h2>
<p>Luckily, Framer Motion makes this very simple. The <code>&lt;motion.path&gt;</code> motion component has an attribute called <code>pathLength</code>. As the name suggests, it controls the length of the visible path. A value of <code>0</code> completely hides the path, and a value of <code>1</code> shows the whole path.</p>
<p>Let's see how that looks in the following example. Press "Refresh Preview" on the CodeSandbox below to trigger the animation again. If you are unfamiliar with CodeSandbox, you can view the code by dragging the slider on the left side of the window.</p>
<iframe src="https://codesandbox.io/embed/path-example-blob-ciuq2m?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<p>By setting the <code>initial={{pathLength: 0}}</code> and the a <code>animate={{pathLength: 1}}</code>, the path animates from completely hidden to completely visible. Framer motion animates this <strong>every time the component is rendered</strong>.</p>
<p>The <code>initial</code> attribute defines the animation <em>target</em> (state) <strong>before</strong> the animation. The <code>animate</code> attribute defines the animation <em>target</em> that the component should animate <strong>to</strong> (the destination). This includes the final resting state of the motion component as well as the transition to get there.</p>
<p>All of the <code>stroke-dasharray</code> and <code>stroke-dashoffset</code> shenanigans we discussed earlier are handled under the hood by Framer Motion.</p>
<h2 id="heading-transitions">Transitions</h2>
<p>Framer Motion has excellent default transitions, making it very fast to slap an animation on an unsuspecting HTML element 👏. But if we want more control, the <code>transition</code> attribute is where we make that happen.</p>
<p>Most importantly, <code>duration</code> allows us to define how long the animation takes from start to end (in seconds). We can let the animation play at a later time using <code>delay</code>.</p>
<p>In the example, we are animating <code>pathLength</code> from an initial value of <code>0</code> to a value of <code>1</code>. You may have noticed that the animation starts slowly, speeds up, and ends slowly. In animation, this is called <em>easing</em> and can control it via the <code>ease</code> option on the <code>transition</code> attribute. For this animation, Framer Motion defaults to <code>"easeInOut"</code>. In contrast, the <code>"linear"</code> value would keep the same speed between <code>0</code> and <code>1</code>. Framer Motion covers most common cases with their <a target="_blank" href="https://www.framer.com/docs/transition/###ease">built-in easing functions</a>. You can even define custom easing functions, but that is for another time.</p>
<p>There are also other transition <em>types</em> controlled by the <code>type</code> option. These include <a target="_blank" href="https://www.framer.com/docs/transition/#tween">tween</a>, <a target="_blank" href="https://www.framer.com/docs/transition/#spring">spring</a> and <a target="_blank" href="https://www.framer.com/docs/transition/#inertia">inertia</a>. Framer Motion will default to different <em>types</em> for different animations. The example above and any other animation that includes an easing function is of type <em>tween</em>. <em>Spring</em> and <em>inertia</em> work by simulating physics and thus have different controls.</p>
<p>You don't need to know all of this to make beautiful animations. I wanted to mention it so that you know what's possible and where to go from here. Let's look at examples of how we can use SVG path animations on the web!</p>
<h2 id="heading-basic-examples">Basic Examples</h2>
<p>Hint: Click "Refresh preview" in the bottom left to see the "Inertia" example animate again.</p>
<iframe src="https://codesandbox.io/embed/path-examples-2-qzpy35?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<h3 id="heading-explanations">Explanations</h3>
<p><strong>Forward</strong><br />The same animation we did above.</p>
<p><strong>Back</strong><br />Instead of animating <code>pathLength</code> from <code>0</code> to <code>1</code>, we do it from <code>1</code> to <code>0</code>.</p>
<p><strong>Back to front</strong><br />We leave <code>pathLength</code> at <code>1</code> and instead animate <code>pathOffset</code> from <code>1</code> to <code>0</code>.</p>
<p><strong>Front to back</strong><br />Same as back to front but moving <code>pathOffset</code> from <code>0</code> to <code>1</code>.</p>
<p><strong>Dashed</strong><br />Just like with <code>stroke-dasharray</code>, we can change the size of the gaps using Framer Motion's <code>pathSpacing</code> attribute. It works the same as <code>pathLength</code>. A number between <code>0</code> and <code>1</code> will determine how large the gap is.</p>
<p><strong>Spring</strong><br />Here we use the transition type <code>spring</code> instead of <code>tween</code>. There are two ways to configure it, but the easy one includes setting <code>bounce</code> and <code>duration</code> values. You can find out more <a target="_blank" href="https://www.framer.com/docs/transition/#spring">here</a>.</p>
<p><strong>Inertia</strong><br />The <code>inertia</code> transition type is used mainly on drag animations and momentum scrolling. There are a lot of values to tweak, and if you are interested, I suggest you read more <a target="_blank" href="https://www.framer.com/docs/transition/#inertia">here</a>.</p>
<p><strong>Looping</strong><br />Typically, the animation runs one time when the component renders. However, we can repeat this animation. Looping an animation is handled in the <code>transition</code> attribute. You can set the <code>repeat</code> option to the number of times you want the animation to play. I set it to <code>Infinity</code> in the examples, so they loop forever. You can also change the <code>repeatType</code> to affect the repeat behaviour. <code>"loop"</code> will start the animation from the start. That is also what I am using above. <code>repeatType: "mirror"</code> will switch the "to" and "from". <code>repeatType: "reverse"</code> alternates forward and backwards playback.</p>
<h2 id="heading-variants">Variants</h2>
<p>So far, we have looked at <code>initial</code> and <code>animate</code> as our two animation <em>targets</em>. An animation <em>target</em> defines what the component should animate <strong>to</strong> and what <strong>transition</strong> it should use to get there.</p>
<p>With variants, we can set <code>animate</code> to one of many targets, depending on the state of our application. Think of a menu that can be open or closed.</p>
<p>Variants allow us to bundle all of these targets into a single object. We can then choose which target to display by setting the <code>animate</code> value to the key of the desired target within the variants object.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { motion } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;

<span class="hljs-keyword">const</span> menuVariants = {
    <span class="hljs-attr">open</span>: {
        <span class="hljs-attr">opacity</span>: <span class="hljs-number">1</span>,
        <span class="hljs-attr">transition</span>: {
            <span class="hljs-attr">duration</span>: <span class="hljs-number">1</span>
        }
    }
    <span class="hljs-attr">closed</span>: {
        <span class="hljs-attr">opacity</span>: <span class="hljs-number">0</span>,
        <span class="hljs-attr">transition</span>: {
            <span class="hljs-attr">duration</span>: <span class="hljs-number">1</span>
        }
    }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Menu</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">return</span>(
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.svg</span>
        <span class="hljs-attr">variants</span>=<span class="hljs-string">{menuVariants}</span>
        <span class="hljs-attr">initial</span>=<span class="hljs-string">"closed"</span>
        <span class="hljs-attr">animate</span>=<span class="hljs-string">"open"</span>
        /&gt;</span></span>
    )
}
</code></pre>
<p>To change the target, we can use state and conditionals:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// Using state with variant name</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Menu</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> [showMenu, setShowMenu] = useState(<span class="hljs-string">"closed"</span>)
    <span class="hljs-keyword">return</span>(
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.SVG</span>
        <span class="hljs-attr">variants</span>=<span class="hljs-string">{menuVariants}</span>
        <span class="hljs-attr">initial</span>=<span class="hljs-string">"closed"</span>
        <span class="hljs-attr">animate</span>=<span class="hljs-string">{showMenu}</span>
        /&gt;</span></span>
    )
}

<span class="hljs-comment">// Using boolean state</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Menu</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> [showMenu, setShowMenu] = useState(<span class="hljs-literal">false</span>)
    <span class="hljs-keyword">return</span>(
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.svg</span>
        <span class="hljs-attr">variants</span>=<span class="hljs-string">{menuVariants}</span>
        <span class="hljs-attr">initial</span>=<span class="hljs-string">"closed"</span>
        <span class="hljs-attr">animate</span>=<span class="hljs-string">{showMenu</span> ? "<span class="hljs-attr">open</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">closed</span>"}
        /&gt;</span></span>
    )
}
</code></pre>
<p>There are many use cases for variants, including <a target="_blank" href="https://www.framer.com/docs/animation/##orchestration">orchestration</a>, that I will not be getting into here. Let's look at advanced examples to see what we can do with variants.</p>
<h2 id="heading-advanced-examples">Advanced Examples</h2>
<iframe src="https://codesandbox.io/embed/path-examples-advanced-jwe9t3?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<h3 id="heading-explanations-1">Explanations</h3>
<p><strong>Logo</strong><br />Believe it or not, this is the same animation as the "Forward" example from the "basic" section. No fancy variants or anything. I put it here to show you that animations don't need to be complicated to be impactful.</p>
<p>It's only a simple <code>pathLength</code> animation going from <code>0</code> to <code>1</code>. The only difference is the <code>&lt;path&gt;</code> I was using. The more interesting the shape of the <code>&lt;path&gt;</code>, the more interesting the animation. In this case, I applied the animation to <code>&lt;motion.line&gt;</code> , <code>&lt;motion.rect&gt;</code>, <code>&lt;motion.circle&gt;</code> and <code>&lt;motion.polygon&gt;</code> elements.</p>
<p>You might be saying, "but these are not paths!". You are right, but it doesn't matter. It works just the same. All SVG lines are just vectors going from one coordinate of the viewBox to the next and can therefore be animated this way. If you want to know more about how SVGs use coordinates, check out my <a target="_blank" href="https://blog.noelcserepy.com/3-concepts-to-master-svgs">guide to SVG scaling</a>.</p>
<p><strong>Toggle</strong><br />There are two <code>&lt;path&gt;</code>'s here. One is for the larger pill shape acting as the background. The other one is the moving pill shape in the foreground. Both <code>&lt;path&gt;</code> elements are just a single line. A single <strong>fat</strong> line. To make them look pill-shaped, I increased the <code>strokeWidth</code> and set <code>strokeLinecap="rounded"</code>. I used state to switch between <em>on</em> and <em>off</em> variants. When <em>on</em>, I increase <code>pathOffset</code> and change the colour to orange. Note that I am also using a new easing function.</p>
<p><strong>Hover Button</strong><br />Okay, this one is a bit more complex. I am using three elements:</p>
<ul>
<li><p>a <code>&lt;div&gt;</code> that acts as a container</p>
</li>
<li><p>a <code>&lt;path&gt;</code> forming a rectangle and spanning the size of the container</p>
</li>
<li><p>a <code>&lt;p&gt;</code> that displays the text</p>
</li>
</ul>
<p>I used <code>whileHover</code> to detect the hover event. <code>whileHover</code> is one of Framer Motion's <a target="_blank" href="https://www.framer.com/docs/gestures/"><em>gestures</em></a>. Gestures allow you to define animation targets for one of several events (like hover, focus, scroll etc.). However, applying the gesture to the <code>&lt;path&gt;</code> directly will not work because the <code>pathLength</code> is <code>0</code> and, therefore, is hard to hover over.</p>
<p>To work around this, I applied <code>whileHover</code> to the parent <code>&lt;div&gt;</code> element. Motion components <strong>inherit</strong> <code>initial</code> and <code>animate</code> attributes from their parent components. Thus if we do <strong>not</strong> set those attributes on our <code>&lt;path&gt;</code> element, the animation target from <code>&lt;div&gt;</code> will be inherited.</p>
<p>Using variants, I defined the animation targets <code>"hover"</code> and <code>"default"</code>. While hovered, the <code>pathLength</code> moves to <code>1</code>, the <code>strokeWidth</code> increases and the <code>color</code> changes to orange.</p>
<p>Although the <code>&lt;p&gt;</code> element uses a different variants object, I used the same names for the targets (<code>"hover"</code> and <code>"default"</code>). Because <code>&lt;p&gt;</code> is also a child of our parent <code>&lt;div&gt;</code>, it can also inherit the <code>animate</code> attribute.</p>
<p>Hovering the parent <code>&lt;div&gt;</code> will activate different animations in the text and the path using the same <code>animate</code> values. Isn't that cool?</p>
<h2 id="heading-a-quick-note-on-ssr">A quick note on SSR</h2>
<p>If you plan on server-side rendering your application, add <code>strokeDasharray="0 1"</code> to your motion component. Framer Motion needs JavaScript to measure the total length of the path for most path animations. Because of that, the element is loaded onto the DOM before the animation can hide the element. This causes undesirable flickering on page loads. Setting <code>strokeDasharray="0 1"</code> ensures that the path is hidden before the first render.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We've covered a lot of ground in this article. We discovered how path animations work, how to implement path animations with framer motion, transitions, motion components, variants and some advanced techniques with inheritance. Play around with what you have learned, and share with me what you came up with!</p>
<p>If you want to support me, the best way is to check out my new app at <a target="_blank" href="https://www.covercraft.io/">https://www.covercraft.io/</a>. If you're struggling to find the right words for your cover letter or get tired of re-formulating how great your experience matches the job description, look no further. I appreciate your support!</p>
]]></content:encoded></item><item><title><![CDATA[The Interactive Guide to SVGs: Viewport, ViewBox and PreserveAspectRatio]]></title><description><![CDATA[What are SVGs?
Scalable Vector Graphics (SVG) are a method computers use to describe an image. Just like any other image format.
Unlike many other image formats, the SVG format is text-based. More precisely, SVGs are XML-based. This opens up how we c...]]></description><link>https://blog.noelcserepy.com/the-interactive-guide-to-svgs-viewport-viewbox-and-preserveaspectratio</link><guid isPermaLink="true">https://blog.noelcserepy.com/the-interactive-guide-to-svgs-viewport-viewbox-and-preserveaspectratio</guid><category><![CDATA[SVG]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[graphic design]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[CSS]]></category><dc:creator><![CDATA[Noël Cserépy]]></dc:creator><pubDate>Wed, 07 Sep 2022 13:42:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662557825688/tQb9V9C4F.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-are-svgs">What are SVGs?</h2>
<p><strong><em>Scalable Vector Graphics</em> (SVG)</strong> are a method computers use to describe an image. Just like any other image format.</p>
<p>Unlike many other image formats, the SVG format is text-based. More precisely, SVGs are XML-based. This opens up how we can interact with the image. It allows us to integrate SVGs with other Web standards such as HTML, CSS and JavaScript. </p>
<blockquote>
<p><em>"SVG is, essentially, to graphics what HTML is to text."</em>
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG">MDN docs</a></p>
</blockquote>
<h3 id="heading-how-do-svgs-work">How do SVGs work?</h3>
<p>Image files like <code>.jpeg</code> and <code>.png</code> are <em>raster-based</em>. That means they use millions of tiny dots called pixels to define the image's appearance. For every pixel, they define a red, green and blue value, which then gets displayed on the screen. The more pixels they define, the higher the resolution of the image. </p>
<p>SVGs take a different approach. Vector graphics like <strong>SVGs use rules</strong> to describe how the image should look.</p>
<p>Instead of storing values for millions of pixels, an SVG creates a <em>viewport</em>. You can think of it like a canvas. It then specifies points on that <em>viewport</em> along with instructions on how to connect those points. It is up to the computer to calculate where to paint. This rule-based format gives them the ability to scale without losing quality. Hence, <em>Scalable</em> Vector Graphic.</p>
<h3 id="heading-why-use-svgs">Why use SVGs?</h3>
<p>Because SVGs are rule-based and calculated on the fly by the computer, they can:</p>
<ul>
<li>scale indefinitely without losing quality</li>
<li>fit any screen size (great for responsive)</li>
<li>have tiny file sizes and thus load very quickly</li>
<li>trigger fewer HTTP requests</li>
<li><strong>be edited and created in code</strong></li>
<li><strong>have individually addressable parts (like a circle or line)</strong></li>
<li><strong>be animated!</strong></li>
</ul>
<p>These last 3 points are alluring because they give us more creative control over the final image.</p>
<h3 id="heading-adding-svgs-to-the-web">Adding SVGs to the Web</h3>
<p>There are many ways to incorporate SVG images into HTML, including:</p>
<ul>
<li>as a <code>src</code> to an <code>&lt;img&gt;</code> tag</li>
<li>in an <code>&lt;object&gt;</code> tag</li>
<li>in an <code>&lt;iframe&gt;</code> tag</li>
<li>using the <code>&lt;svg&gt;</code> tag</li>
</ul>
<p>We will focus on the <code>&lt;svg&gt;</code> tag for our purposes. If you're interested in learning about the others, there is an excellent <a target="_blank" href="https://css-tricks.com/using-svg/">CSS Tricks article</a> on this subject.</p>
<h2 id="heading-the-element">The <code>&lt;svg&gt;</code> Element</h2>
<p>The <code>&lt;svg&gt;</code> element is the container for our image. It can hold all other elements, such as lines, circles, paths and even other <code>&lt;svg&gt;</code> elements. </p>
<p>To fully grasp how it works, there are <strong>3 crucial concepts</strong> that we need to cover first, namely:</p>
<ul>
<li>Viewport</li>
<li>The <code>viewBox</code> attribute</li>
<li>The <code>preserveAspectRatio</code> attribute</li>
</ul>
<p>If you nail these three concepts, everything else will fall into place. Are you ready? </p>
<p>Okay, let's go!</p>
<h2 id="heading-viewport">Viewport</h2>
<p>The viewport is the area of a computer graphic that is currently visible. Think of the display area of an editing tool, a computer game or your favourite web browser.</p>
<h3 id="heading-the-browser-viewport">The Browser Viewport</h3>
<p>The viewport for web browsers is usually the same as the browser window. That means everything except the browser UI, such as tabs, address bar, etc. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662555759352/kRrJECnM8.png" alt="viewport.png" /></p>
<p>Any content that does not fit inside the viewport is not visible on-screen (like the footer of a long website). It comes into view only when you scroll your viewport down to that part of the content. </p>
<p>Items can be positioned on the browser viewport using CSS pixels as a coordinate system. The <strong>coordinates start at the top left of the viewport</strong>. To move to the right, increase the x value. Move down by increasing the y value. </p>
<pre><code class="lang-CSS"><span class="hljs-selector-tag">div</span> {
    <span class="hljs-attribute">position</span>: absolute;
    <span class="hljs-attribute">top</span>: <span class="hljs-number">100px</span>;
    <span class="hljs-attribute">left</span>: <span class="hljs-number">50px</span>;
}
</code></pre>
<h3 id="heading-svg-viewports">SVG Viewports</h3>
<p>Viewports can be nested inside of each other. In fact, <strong>whenever you make a new <code>&lt;svg&gt;</code> element, it creates a new viewport and coordinate system</strong> inside the current viewport. </p>
<p>You can define the size and placement of the <code>&lt;svg&gt;</code> viewport using CSS, like any other HTML element. This is very handy because it allows you to use flexbox and flex grid to make SVGs responsive. Because they scale without losing quality, you can use the same graphic for all screen sizes!</p>
<p>Another way to adjust the viewport size is by using the <code>width</code> and <code>height</code> attributes of the <code>&lt;svg&gt;</code> element. This will change the size of the viewport but <strong>not the coordinate system</strong>. I avoid the <code>width</code>, and <code>height</code> attributes because styling <code>&lt;svg&gt;</code> with CSS classes gives me more <em>flex</em>ibility (pun intended). Note that setting width and height using CSS will override the <code>width</code> and <code>height</code> attributes on the <code>&lt;svg&gt;</code>.</p>
<p>Check out the interactive demo below.</p>
<div class="hn-embed-widget" id="viewport-example"></div><p>In the above example, I defined the length using percentages. You can set width and height values for top-level SVGs using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/length">length unit identifiers</a> such as <code>px</code>, <code>em</code>, <code>cm</code>, <code>mm</code>, <code>in</code>, and percentages. The demo has two top-level SVGs: the "SVG Viewport" and the "Second SVG Viewport".</p>
<p>For the "Inner SVG Viewport" and all other nested elements, use <em>user units</em> and percentages. </p>
<h3 id="heading-user-units">User Units</h3>
<p> A value without a unit is called a <em>user unit</em>. User units used on children of the browser window viewport will be interpreted as the same number of <code>px</code> units. </p>
<p>Once you have defined a viewport for an <code>&lt;svg&gt;</code> element, any child element of that SVG will use the <code>&lt;svg&gt;</code>'s <em>user coordinate system</em>. This is referred to as <em>user space</em>.</p>
<p>Any <em>user units</em> on SVG children will be converted to units of the <code>&lt;svg&gt;</code>'s coordinate system. Percentages will be interpreted as a percentage of the parent's viewport.</p>
<pre><code class="lang-HTML"><span class="hljs-comment">&lt;!-- viewport will be 100px by 200px in the --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"100"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"200"</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- SVG content using SVG coordinates --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
</code></pre>
<p>In short:
When defining the <strong>outermost SVG</strong>, use <strong>length unit identifiers</strong>.
When defining a <strong>child element</strong>, use <strong>user units and percentages</strong>. </p>
<h2 id="heading-the-viewbox-attribute">The <code>viewBox</code> attribute</h2>
<p>Remember how every <code>&lt;svg&gt;</code> element creates a new viewport and coordinate system? In the section above, we used the <code>&lt;svg&gt;</code> element's <code>width</code> and <code>height</code> to determine the viewport's size. Now we will use <code>viewBox</code> to specify the coordinate system. </p>
<p>Think of Google Maps:
The viewport determines how big the map looks on your screen. 
The viewBox determines which <strong>part</strong> of the map you are looking at and how zoomed in you are.</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"&lt;min-x&gt; &lt;min-y&gt; &lt;width&gt; &lt;height&gt;"</span>&gt;</span>
</code></pre>
<p>It takes four numbers to define the viewBox. </p>
<p>The first two numbers (<code>&lt;min-x&gt;</code> and <code>&lt;min-y&gt;</code>) denote the upper left corner of the coordinate system. Which part of the map do we want to show on Google Maps?</p>
<p>The second pair of numbers (<code>&lt;width&gt;</code> and <code>&lt;height&gt;</code>) define the width and height of the viewBox in user units. How many kilometres across and down should the map show?</p>
<p>Every element we place inside the <code>&lt;svg&gt;</code> tag will use the coordinate system to determine its position and size.</p>
<p>If this sounds confusing, don't worry. Play around with the interactive demo below and get a feeling for what each value does. </p>
<div class="hn-embed-widget" id="viewbox-example"></div><p>In this demo, the viewport has a fixed height and width. The grid shows the size and position of the viewBox.</p>
<p>Notice how moving the "Min-x" and "Min-y" sliders of the viewBox will <em>pan</em> the image across the viewport. Even negative values are possible. </p>
<p>When we link the "Width &amp; Height" slider, we get a <em>zoom</em> effect.</p>
<p>See how the rectangle uses the units of our viewBox coordinate system. 
If you manage to move the rectangle outside of the bounds of the viewBox, it will still be visible. The viewBox will not clip any content beyond its width or height because its only job is to establish a coordinate system. </p>
<p>If you make the width much larger than the height, you will notice that the grid does not entirely cover the viewport. This is because viewBox preserves its aspect ratio by default. We can control where in the viewport to display the grid using the <code>preserveAspectRatio</code> attribute.</p>
<h2 id="heading-the-preserveaspectratio-attribute">The <code>preserveAspectRatio</code> attribute</h2>
<p>The <code>preserveAspectRatio</code> attribute affects how the viewBox is <strong>scaled and positioned</strong> inside a viewport. The syntax looks like this:</p>
<pre><code class="lang-HTML">preserveAspectRatio="<span class="hljs-tag">&lt;<span class="hljs-name">align</span>&gt;</span> [<span class="hljs-tag">&lt;<span class="hljs-name">meetOrSlice</span>&gt;</span>]"
</code></pre>
<p>You can define three things using this attribute:</p>
<ul>
<li>Whether to scale the viewBox <em>uniformly</em> or stretch the viewBox to fit the viewport (by setting <code>preserveAspectRatio="none"</code>)</li>
<li>Whether the whole viewBox should be visible inside the viewport (by defining the <code>&lt;meetOrSlice&gt;</code> value)</li>
<li>How to align the viewBox within the viewport (by defining the <code>&lt;align&gt;</code> value)</li>
</ul>
<p>Let's go through them one by one. The following interactive demo lets you play with all values of the <code>preserveAspectRatio</code> attribute. Refer back to this demo after each of the following sections to visualize what each value does. </p>
<div class="hn-embed-widget" id="par-example"></div><h3 id="heading-none"><code>none</code></h3>
<p><code>none</code> is one of the ten values that <code>&lt;align&gt;</code> can take. The <code>meetOrSlice</code> value is ignored when setting <code>preserveAspectRatio="none"</code>. If you set <code>preserveAspectRatio="none"</code>, then the <strong>viewBox will not scale <em>uniformly</em></strong>. That means that your viewBox will stretch and squish to match the dimensions of your viewport. Setting <code>preserveAspectRatio</code> to <strong>any other value will scale the viewBox <em>uniformly</em></strong>, preserving its aspect ratio. </p>
<h3 id="heading-yga"><code>&lt;meetOrSlice&gt;</code></h3>
<p>There are two choices here:</p>
<h4 id="heading-1-meet-default">1. <code>meet</code> (default)</h4>
<p>Force the whole viewBox to be visible inside the viewport, but have some empty space if the aspect ratios don't match.</p>
<p>If you are familiar with background images, <code>meet</code> is like <code>background-size: contain</code>. The entire viewBox will be visible inside the viewport. If the viewBox and viewport aspect ratio is not the same, the viewBox will not cover the whole viewport. The boundaries of the viewBox and viewport "meet" to fit the viewBox entirely inside the viewport.</p>
<h4 id="heading-2-slice">2. <code>slice</code></h4>
<p>Make the viewBox cover the entire viewport, but <em>slice</em> off the parts that stick out.</p>
<p><code>slice</code> is like <code>background-size: cover</code>. Here the viewBox will scale to cover the entire viewport. If the viewBox and viewport aspect ratio are not the same, parts of the viewBox will be outside the viewport and thus <em>sliced</em> off.</p>
<p>Again, the <code>meetOrSlice</code> value is ignored when setting <code>preserveAspectRatio="none"</code>.</p>
<h3 id="heading-yga-1"><code>&lt;align&gt;</code></h3>
<p><code>&lt;align&gt;</code> can be one of 10 values. We discussed <code>none</code> earlier, so we only need to cover the remaining nine values. As we mentioned earlier, all nine values will preserve the aspect ratio of our viewBox and scale <em>uniformly</em>. These nine values concern themselves only with the <em>alignment</em> of the viewBox. </p>
<p>To align the viewBox with the viewport, <code>&lt;align&gt;</code> uses axes. We can use these nine values to define which axes from the viewBox and viewport should line up. For the viewport, there are six axes in total to choose from. </p>
<ul>
<li>3 axes across (<code>min-x</code>, <code>mid-x</code>, <code>max-x</code>) </li>
<li>3 axes down (<code>min-y</code>, <code>mid-y</code>, <code>max-y</code>). </li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662555794645/CbxsrmTv_.png" alt="viewport-axes.png" /></p>
<p>The "min" and "max" axes are at the outer edges of the viewport. The "mid" axes are at the midpoint between "min" and "max". </p>
<p>The same happens for the viewBox: 6 axes total, three across, three down. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662555804865/-Ztze9r-2.png" alt="viewbox-axes.png" /></p>
<p>So far, so good?</p>
<p>Using this, we can tell the SVG which axes from the viewport, and the viewBox should <em>align</em>. The images above show that the viewBox's "mid-x" axis aligns with the viewport's "mid-x" axis. The viewBox's "mid-y" axis aligns with the viewport's "mid-y" axis. This alignment centres the viewBox inside the viewport. We can define this behaviour using the value <code>xMidYmid</code>, which is also the default value for <code>&lt;align&gt;</code>. </p>
<p>You can align the viewBox using any of these axes. Instead of listing all nine values, I urge you to check out the interactive demo at the start of this section. See how the axes line up when using different <code>&lt;align&gt;</code> and <code>&lt;meetOrSlice&gt;</code> values.</p>
<p>Note that some values of <code>&lt;align&gt;</code> seem to have the same effect. Try changing the size of the viewport to see their differences. For a complete list of values, see the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio#syntax">docs</a>.</p>
<h2 id="heading-the-xmlns-attribute">The <code>xmlns</code> Attribute</h2>
<p>I want to draw your attention to one last thing. You might have seen something like <code>xmlns="http://www.w3.org/2000/svg"</code> on SVGs around the Web. The <code>xmlns</code> attribute defines the XML namespace to avoid name collisions. Don't worry about this too much. All you need to know is that you must add this attribute only to the outermost <code>&lt;svg&gt;</code> element. If you need further clarification, check out <a target="_blank" href="https://stackoverflow.com/a/1181936">this explanation</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>And that's it! You now know the three most important concepts for SVGs! All SVG shapes, paths and masks use these three concepts to determine their appearance. If you want to dig deeper, here is a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element">complete list</a> of SVG elements to explore!</p>
]]></content:encoded></item></channel></rss>