Tag: Svelte

Coordinating Svelte Animations With XState

This post is an introduction to XState as it might be used in a Svelte project. XState is unique in the JavaScript ecosystem. It doesn’t keep your DOM synced with your application state, nor does it help you with asynchrony, or streams of data; XState helps manage your application’s state by allowing you to model your state as a finite state machine (FSM).

A deep dive into state machines and formal languages is beyond the scope of this post, but Jon Bellah does that in another CSS-Tricks article. For now, think of an FSM as a flow chart. Flow charts have a number of states, represented as bubbles, and arrows leading from one state to the next, signifying a transition from one state to the next. State machines can have more than one arrow leading out of a state, or none at all if it’s a final state, and they can even have arrows leaving a state, and pointing right back into that same state.

If that all sounds overwhelming, relax, we’ll get into all the details, nice and slow. For now, the high level view is that, when we model our application as a state machine, we’ll be creating different “states” our application can be in (get it … state machine … states?), and the events that happen and cause changes to state will be the arrows between those states. XState calls the states “states,” and the arrows between the states “actions.”

Our example

XState has a learning curve, which makes it challenging to teach. With too contrived a use case it’ll appear needlessly complex. It’s only when an application’s code gets a bit tangled that XState shines. This makes writing about it tricky. With that said, the example we’ll look at is an autocomplete widget (sometimes called autosuggest), or an input box that, when clicked, reveals a list of items to choose from, which filter as you type in the input.

For this post we’ll look at getting the animation code cleaned up. Here’s the starting point:

This is actual code from my svelte-helpers library, though with unnecessary pieces removed for this post. You can click the input and filter the items, but you won’t be able to select anything, “arrow down” through the items, hover, etc. I’ve removed all the code that’s irrelevant to this post.

We’ll be looking at the animation of the list of items. When you click the input, and the results list first renders, we want to animate it down. As you type and filter, changes to the list’s dimensions will animate larger and smaller. And when the input loses focus, or you click ESC, we animate the list’s height to zero, while fading it out, and then remove it from the DOM (and not before). To make things more interesting (and nice for the user), let’s use a different spring configuration for the opening than what we use for the closing, so the list closes a bit more quickly, or stiffly, so unneeded UX doesn’t linger on the screen too long.

If you’re wondering why I’m not using Svelte transitions to manage the animations in and out of the DOM, it’s because I’m also animating the list’s dimensions when it’s open, as the user filters, and coordinating between transition, and regular spring animations is a lot harder than simply waiting for a spring update to finish getting to zero before removing an element from the DOM. For example, what happens if the user quickly types and filters the list, as it’s animating in? As we’ll see, XState makes tricky state transitions like this easy.

Scoping the Problem

Let’s take a look at the code from the example so far. We’ve got an open variable to control when the list is open, and a resultsListVisible property to control whether it should be in the DOM. We also have a closing variable that controls whether the list is in the process of closing.

On line 28, there’s an inputEngaged method that runs when the input is clicked or focused. For now let’s just note that it sets open and resultsListVisible to true. inputChanged is called when the user types in the input, and sets open to true. This is for when the input is focused, the user clicks escape to close it, but then starts typing, so it can re-open. And, of course, the inputBlurred function runs when you’d expect, and sets closing to true, and open to false.

Let’s pick apart this tangled mess and see how the animations work. Note the slideInSpring and opacitySpring at the top. The former slides the list up and down, and adjusts the size as the user types. The latter fades the list out when hidden. We’ll focus mostly on the slideInSpring.

Take a look at the monstrosity of a function called setSpringDimensions. This updates our slide spring. Focusing on the important pieces, we take a few boolean properties. If the list is opening, we set the opening spring config, we immediately set the list’s width (I want the list to only slide down, not down and out), via the { hard: true } config, and then set the height. If we’re closing, we animate to zero, and, when the animation is complete, we set resultsListVisible to false (if the closing animation is interrupted, Svelte will be smart enough to not resolve the promise so the callback will never run). Lastly, this method is also called any time the size of the results list changes, i.e., as the user filters. We set up a ResizeObserver elsewhere to manage this.

Spaghetti galore

Let’s take stock of this code.

  • We have our open variable which tracks if the list is open.
  • We have the resultsListVisible variable which tracks if the list should be in the DOM (and set to false after the close animation is complete).
  • We have the closing variable that tracks if the list is in the process of closing, which we check for in the input focus/click handler so we can reverse the closing animation if the user quickly re-engages the widget before it’s done closing.
  • We also have setSpringDimensions that we call in four different places. It sets our springs depending on whether the list is opening, closing, or just resizing while open (i.e. if the user filters the list).
  • Lastly, we have a resultsListRendered Svelte action that runs when the results list DOM element renders. It starts up our ResizeObserver, and when the DOM node unmounts, sets closing to false.

Did you catch the bug? When the ESC button is pressed, I’m only setting open to false. I forgot to set closing to true, and call setSpringDimensions(false, true). This bug was not purposefully contrived for this blog post! That’s an actual mistake I made when I was overhauling this widget’s animations. I could just copy paste the code in inputBlured over to where the escape button is caught, or even move it to a new function and call it from both places. This bug isn’t fundamentally hard to solve, but it does increase the cognitive load of the code.

There’s a lot of things we’re keeping track of, but worst of all, this state is scattered all throughout the module. Take any piece of state described above, and use CodeSandbox’s Find feature to view all the places where that piece of state is used. You’ll see your cursor bouncing across the file. Now imagine you’re new to this code, trying to make sense of it. Think about the growing mental model of all these state pieces that you’ll have to keep track of, figuring out how it works based on all the places it exists. We’ve all been there; it sucks. XState offers a better way; let’s see how.

Introducing XState

Let’s step back a bit. Wouldn’t it be simpler to model our widget in terms of what state it’s in, with events happening as the user interacts, which cause side effects, and transitions to new states? Of course, but that’s what we were already doing; the problem is, the code is scattered everywhere. XState gives us the ability to properly model our state in this way.

Setting expectations

Don’t expect XState to magically make all of our complexity vanish. We still need to coordinate our springs, adjust the spring’s config based on opening and closing states, handle resizes, etc. What XState gives us is the ability to centralize this state management code in a way that’s easy to reason about, and adjust. In fact, our overall line count will increase a bit, as a result of our state machine setup. Let’s take a look.

Your first state machine

Let’s jump right in, and see what a bare bones state machine looks like. I’m using XState’s FSM package, which is a minimal, pared down version of XState, with a tiny 1KB bundle size, perfect for libraries (like an autosuggest widget). It doesn’t have a lot of advanced features like the full XState package, but we wouldn’t need them for our use case, and we wouldn’t want them for an introductory post like this.

The code for our state machine is below, and the interactive demo is over at Code Sandbox. There’s a lot, but we’ll go over it shortly. And to be clear, it doesn’t work yet.

const stateMachine = createMachine(   {     initial: "initial",     context: {       open: false,       node: null     },     states: {       initial: {         on: { OPEN: "open" }       },       open: {         on: {           RENDERED: { actions: "rendered" },           RESIZE: { actions: "resize" },           CLOSE: "closing"         },         entry: "opened"       },       closing: {         on: {           OPEN: { target: "open", actions: ["resize"] },           CLOSED: "closed"         },         entry: "close"       },       closed: {         on: {           OPEN: "open"         },         entry: "closed"       }     }   },   {     actions: {       opened: assign(context => {         return { ...context, open: true };       }),       rendered: assign((context, evt) => {         const { node } = evt;         return { ...context, node };       }),       close() {},       resize(context) {},       closed: assign(() => {         return { open: false, node: null };       })     }   } );

Let’s go from top to bottom. The initial property controls what the initial state is, which I’ve called “initial.” context is the data associated with our state machine. I’m storing a boolean for whether the results list is currently open, as well as a node object for that same results list. Next we see our states. Each state is a key in the states property. For most states, you can see we have an on property, and an entry property.

on configures events. For each event, we can transition to a new state; we can run side effects, called actions; or both. For example, when the OPEN event happens inside of the initial state, we move into the open state. When the RENDERED event happens in the open state, we run the rendered action. And when the OPEN event happens inside the closing state, we transition into the open state, and also run the resize action. The entry field you see on most states configures an action to run automatically whenever a state is entered. There are also exit actions, although we don’t need them here.

We still have a few more things to cover. Let’s look at how our state machine’s data, or context, can change. When we want an action to modify context, we wrap it in assign and return the new context from our action; if we don’t need any processing, we can just pass the new state directly to assign. If our action does not update context, i.e., it’s just for side effects, then we don’t wrap our action function in assign, and just perform whatever side effects we need.

Affecting change in our state machine

We have a cool model for our state machine, but how do we run it? We use the interpret function.

const stateMachineService = interpret(stateMachine).start();

Now stateMachineService is our running state machine, on which we can invoke events to force our transitions and actions. To fire an event, we call send, passing the event name, and then, optionally, the event object. For example, in our Svelte action that runs when the results list first mounts in the DOM, we have this:

stateMachineService.send({ type: "RENDERED", node });

That’s how the rendered action gets the node for the results list. If you look around the rest of the AutoComplete.svelte file, you’ll see all the ad hoc state management code replaced with single line event dispatches. In the event handler for our input click/focus, we run the OPEN event. Our ResizeObserver fires the RESIZE event. And so on.

Let’s pause for a moment and appreciate the things XState gives us for free here. Let’s look at the handler that runs when our input is clicked or focused before we added XState.

function inputEngaged(evt) {   if (closing) {     setSpringDimensions();   }   open = true;   resultsListVisible = true; } 

Before, we were checking to see if we were closing, and if so, forcing a re-calculation of our sliding spring. Otherwise we opened our widget. But what happened if we clicked on the input when it was already open? The same code re-ran. Fortunately that didn’t really matter. Svelte doesn’t care if we re-set open and resultsListVisible to the values they already held. But those concerns disappear with XState. The new version looks like this:

 function inputEngaged(evt) {   stateMachineService.send("OPEN"); }

If our state machine is already in the open state, and we fire the OPEN event, then nothing happens, since there’s no OPEN event configured for that state. And that special handling for when the input is clicked when the results are closing? That’s also handled right in the state machine config — notice how the OPEN event tacks on the resize action when it’s run from the closing state.

And, of course, we’ve fixed the ESC key bug from before. Now, pressing the key simply fires the CLOSE event, and that’s that.

Finishing up

The ending is almost anti-climactic. We need to take all of the work we were doing before, and simply move it to the right place among our actions. XState does not remove the need for us to write code; it only provides a structured, clear place to put it.

{   actions: {     opened: assign({ open: true }),     rendered: assign((context, evt) => {       const { node } = evt;       const dimensions = getResultsListDimensions(node);       itemsHeightObserver.observe(node);       opacitySpring.set(1, { hard: true });       Object.assign(slideInSpring, SLIDE_OPEN);       slideInSpring.update(prev => ({ ...prev, width: dimensions.width }), {         hard: true       });       slideInSpring.set(dimensions, { hard: false });       return { ...context, node };     }),     close() {       opacitySpring.set(0);       Object.assign(slideInSpring, SLIDE_CLOSE);       slideInSpring         .update(prev => ({ ...prev, height: 0 }))         .then(() => {           stateMachineService.send("CLOSED");         });     },     resize(context) {       opacitySpring.set(1);       slideInSpring.set(getResultsListDimensions(context.node));     },     closed: assign(() => {       itemsHeightObserver.unobserve(resultsList);       return { open: false, node: null };     })   } }

Odds and ends

Our animation state is in our state machine, but how do we get it out? We need the open state to control our results list rendering, and, while not used in this demo, the real version of this autosuggest widget needs the results list DOM node for things like scrolling the currently highlighted item into view.

It turns out our stateMachineService has a subscribe method that fires whenever there’s a state change. The callback you pass is invoked with the current state machine state, which includes a context object. But Svelte has a special trick up its sleeve: its reactive syntax of $ : doesn’t only work with component variables and Svelte stores; it also works with any object with a subscribe method. That means we can sync with our state machine with something as simple as this:

$  : ({ open, node: resultsList } = $  stateMachineService.context);

Just a regular destructuring, with some parens to help things get parsed correctly.

One quick note here, as an area for improvement. Right now, we have some actions which both both perform a side effect, and also update state. Ideally, we should probably split these up into two actions, one just for the side effect, and the other using assign for the new state. But I decided to keep things as simple as possible for this article to help ease the introduction of XState, even if a few things wound up not being quite ideal.

Parting thoughts

I hope this post has sparked some interest in XState. I’ve found it to be an incredibly useful, easy to use tool for managing complex state. Please know that we’ve only scratched the surface. We focused on the minimal fsm package, but the entire XState library is capable of a lot more than what we covered here, from nested states, to first-class support for Promises, and it even has a state visualization tool! I urge you to check it out.

Happy coding!


The post Coordinating Svelte Animations With XState appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,

How to Use Tailwind on a Svelte Site

Let’s spin up a basic Svelte site and integrate Tailwind into it for styling. One advantage of working with Tailwind is that there isn’t any context switching going back and forth between HTML and CSS, since you’re applying styles as classes right on the HTML. It’s all the in same file in Svelte anyway, but still, this way you don’t even need a <style> section in your .svelte files.

If you are a Svelte developer or enthusiast, and you’d like to use Tailwind CSS in your Svelte app, this article looks at the easiest, most-straightforward way to install tailwind in your app and hit the ground running in creating a unique, modern UI for your app.

If you like to just see a working example, here’s a working GitHub repo.

Why Svelte?

Performance-wise, Svelte is widely considered to be one of the top JavaScript frameworks on the market right now. Created by Rich Harris in 2016, it has been growing rapidly and becoming popular in the developer community. This is mainly because, while very similar to React (and Vue), Svelte is much faster. When you create an app with React, the final code at build time is a mixture of React and vanilla JavaScript. But browsers only understand vanilla JavaScript. So when a user loads your app in a browser (at runtime), the browser has to download React’s library to help generate the app’s UI. This slows down the process of loading the app significantly.

How’s Svelte different? It comes with a compiler that compiles all your app code into vanilla JavaScript at build time. No Svelte code makes it into the final bundle. In this instance, when a user loads your app, their browser downloads only vanilla JavaScript files, which are lighter. No framework UI library is needed. This significantly speeds up the process of loading your app. For this reason, Svelte applications are usually very small and lightning fast.

The only downside Svelte currently faces is that since it’s still new and doesn’t have the kind of ecosystem and community backing that more established frameworks like React enjoy.

Why Tailwind?

Tailwind CSS is a CSS framework. It’s somewhat similar to popular frameworks, like Bootstrap and Materialize, in that you apply classes to elements and it styles them. But it is also atomic CSS in that one class name does one thing. While Tailwind does have Tailwind UI for pre-built componentry, generally you customize Tailwind to look how you want it to look, so there is less risk of “looking like a Bootstrap site” (or whatever other framework that is less commonly customized).

For example, rather than give you a generic header component that comes with some default font sizes, margins, paddings, and other styling, Tailwind provides you with utility classes for different font sizes, margins, and paddings. You can pick the specific ones you want and create a unique looking header with them.

Tailwind has other advantages as well:

  • It saves you the time and stress of writing custom CSS yourself. With Tailwind, you get thousands of out-of-the-box CSS classes that you just need to apply to your HTML elements.
  • One thing most users of Tailwind appreciate is the naming convention of the utility classes. The names are simple and they do a good job of telling you what their functions are. For example, text-sm gives your text a small font size**.** This is a breath of fresh air for people that struggle with naming custom CSS classes.
  • By utilizing a mobile-first approach, responsiveness is at the heart of Tailwind’s design. Making use of the sm, md, and lg prefixes to specify breakpoints, you can control the way styles are rendered across different screen sizes. For example, if you use the md prefix on a style, that style will only be applied to medium-sized screens and larger. Small screens will not be affected.
  • It prioritizes making your application lightweight by making PurgeCSS easy to set up in your app. PurgeCSS is a tool that runs through your application and optimizes it by removing all unused CSS classes, significantly reducing the size of your style file. We’ll use PurgeCSS in our practice project.

All this said Tailwind might not be your cup of tea. Some people believe that adding lots of CSS classes to your HTML elements makes your HTML code difficult to read. Some developers even think it’s bad practice and makes your code ugly. It’s worth noting that this problem can easily be solved by abstracting many classes into one using the @apply directive, and applying that one class to your HTML, instead of the many.

Tailwind might also not be for you if you are someone who prefers ready-made components to avoid stress and save time, or you are working on a project with a short deadline.

Step 1: Scaffold a new Svelte site

Svelte provides us with a starter template we can use. You can get it by either cloning the Svelte GitHub repo, or by using degit. Using degit provides us with certain advantages, like helping us make a copy of the starter template repository without downloading its entire Git history (unlike git clone). This makes the process faster. Note that degit requires Node 8 and above.

Run the following command to clone the starter app template with degit:

npx degit sveltejs/template project-name

Navigate into the directory of the starter project so we can start making changes to it:

cd project-name

The template is mostly empty right now, so we’ll need to install some required npm packages:

npm install

Now that you have your Svelte app ready, you can proceed to combining it with Tailwind CSS to create a fast, light, unique web app.

Step 2: Adding Tailwind CSS

Let’s proceed to adding Tailwind CSS to our Svelte app, along with some dev dependencies that will help with its setup.

npm install tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9   # or  yarn add tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

The three tools we are downloading with the command above:

  1. Tailwind
  2. PostCSS
  3. Autoprefixer

PostCSS is a tool that uses JavaScript to transform and improve CSS. It comes with a bunch of plugins that perform different functions like polyfilling future CSS features, highlighting errors in your CSS code, controlling the scope of CSS class names, etc.

Autoprefixer is a PostCSS plugin that goes through your code adding vendor prefixes to your CSS rules (Tailwind does not do this automatically), using caniuse as reference. While browsers are choosing to not use prefixing on CSS properties the way they had in years past, some older browsers still rely on them. Autoprefixer helps with that backwards compatibility, while also supporting future compatibility for browsers that might apply a prefix to a property prior to it becoming a standard.

For now, Svelte works with an older version of PostCSS. Its latest version, PostCSS 8, was released September 2020. So, to avoid getting any version-related errors, our command above specifies PostCSS 7 instead of 8. A PostCSS 7 compatibility build of Tailwind is made available under the compat channel on npm.

Step 3: Configuring Tailwind

Now that we have Tailwind installed, let’s create the configuration file needed and do the necessary setup. In the root directory of your project, run this to create a tailwind.config.js file:

npx tailwindcss init  tailwind.config.js

Being a highly customizable framework, Tailwind allows us to easily override its default configurations with custom configurations inside this tailwind.config.js file. This is where we can easily customize things like spacing, colors, fonts, etc.

The tailwind.config.js file is provided to prevent ‘fighting the framework’ which is common with other CSS libraries. Rather than struggling to reverse the effect of certain classes, you come here and specify what you want. It’s in this file that we also define the PostCSS plugins used in the project.

The file comes with some default code. Open it in your text editor and add this compatibility code to it:

future: {   purgeLayersByDefault: true,   removeDeprecatedGapUtilities: true, },

Tailwind 2.0 (the latest version), all layers (e.g., base, components, and utilities) are purged by default. In previous versions, however, just the utilities layer is purged. We can manually configure Tailwind to purge all layers by setting the purgeLayersByDefault flag to true.

Tailwind 2.0 also removes some gap utilities, replacing them with new ones. We can manually remove them from our code by setting removeDeprecatedGapUtilities to true.

These will help you handle deprecations and breaking changes from future updates.

PurgeCSS

The several thousand utility classes that come with Tailwind are added to your project by default. So, even if you don’t use a single Tailwind class in your HTML, your project still carries the entire library, making it rather bulky. We’ll want our files to be as small as possible in production, so we can use purge to remove all of the unused utility classes from our project before pushing the code to production.

Since this is mainly a production problem, we specify that purge should only be enabled in production.

purge: {   content: [     "./src/**/*.svelte",   ],   enabled: production // disable purge in dev },

Now, your tailwind.config.js should look like this:

const production = !process.env.ROLLUP_WATCH; module.exports = {   future: {     purgeLayersByDefault: true,     removeDeprecatedGapUtilities: true,   },   plugins: [    ],   purge: {     content: [      "./src/**/*.svelte",      ],     enabled: production // disable purge in dev   }, };

Rollup.js

Our Svelte app uses Rollup.js, a JavaScript module bundler made by Rich Harris, the creator of Svelte, that is used for compiling multiple source files into one single bundle (similar to webpack). In our app, Rollup performs its function inside a configuration file called rollup.config.js.

With Rollup, We can freely break our project up into small, individual files to make development easier. Rollup also helps to lint, prettify, and syntax-check our source code during bundling.

Step 4: Making Tailwind compatible with Svelte

Navigate to rollup.config.js and import the sveltePreprocess package. This package helps us handle all the CSS processing required with PostCSS and Tailwind.

import sveltePreprocess from "svelte-preprocess";

Under plugins, add sveltePreprocess and require Tailwind and Autoprefixer, as Autoprefixer will be processing the CSS generated by these tools.

preprocess: sveltePreprocess({   sourceMap: !production,   postcss: {     plugins: [      require("tailwindcss"),       require("autoprefixer"),     ],   }, }),

Since PostCSS is an external tool with a syntax that’s different from Svelte’s framework, we need a preprocessor to process it and make it compatible with our Svelte code. That’s where the sveltePreprocess package comes in. It provides support for PostCSS and its plugins. We specify to the sveltePreprocess package that we are going to require two external plugins from PostCSS, Tailwind and Autoprefixer. sveltePreprocess runs the foreign code from these two plugins through Babel and converts them to code supported by the Svelte compiler (ES6+). Rollup eventually bundles all of the code together.

The next step is to inject Tailwind’s styles into our app using the @tailwind directive. You can think of @tailwind loosely as a function that helps import and access the files containing Tailwind’s styles. We need to import three sets of styles.

The first set of styles is @tailwind base. This injects Tailwind’s base styles—mostly pulled straight from Normalize.css—into our CSS. Think of the styles you commonly see at the top of stylesheets. Tailwind calls these Preflight styles. They are provided to help solve cross-browser inconsistencies. In other words, they remove all the styles that come with different browsers, ensuring that only the styles you employ are rendered. Preflight helps remove default margins, make headings and lists unstyled by default, and a host of other things. Here’s a complete reference of all the Preflight styles.

The second set of styles is @tailwind components. While Tailwind is a utility-first library created to prevent generic designs, it’s almost impossible to not reuse some designs (or components) when working on a large project. Think about it. The fact that you want a unique-looking website doesn’t mean that all the buttons on a page should be designed differently from each other. You’ll likely use a button style throughout the app.

Follow this thought process. We avoid frameworks, like Bootstrap, to prevent using the same kind of button that everyone else uses. Instead, we use Tailwind to create our own unique button. Great! But we might want to use this nice-looking button we just created on different pages. In this case, it should become a component. Same goes for forms, cards, badges etc.

All the components you create will eventually be injected into the position that @tailwind components occupies. Unlike other frameworks, Tailwind doesn’t come with lots of predefined components, but there are a few. If you aren’t creating components and plan to only use the utility styles, then there’s no need to add this directive.

And, lastly, there’s @tailwind utilities. Tailwind’s utility classes are injected here, along with the ones you create.

Step 5: Injecting Tailwind Styles into Your Site

It’s best to inject all of the above into a high-level component so they’re accessible on every page. You can inject them in the App.svelte file:

<style global lang="postcss">   @tailwind base;   @tailwind components;   @tailwind utilities; </style>

Now that we have Tailwind set up in, let’s create a website header to see how tailwind works with Svelte. We’ll create it in App.svelte, inside the main tag.

This is what we have so far in the browser. Tailwind gives us everything we need to customize this into something unique, like the header we’re about to create.

Step 6: Creating A Website Header

Starting with some basic markup:

<nav>   <div>     <div>       <a href="#">APP LOGO</a>        <!-- Menus -->       <div>         <ul>           <li>             <a href="#">About</a>           </li>           <li>             <a href="#">Services</a>           </li>           <li>             <a href="#">Blog</a>           </li>           <li>             <a href="#">Contact</a>           </li>         </ul>       </div>      </div>   </div> </nav>

This is the header HTML without any Tailwind CSS styling. Pretty standard stuff. We’ll wind up moving the “APP LOGO” to the left side, and the four navigation links on the right side of it.

What we have with zero styling whatsoever.

Now let’s add some Tailwind CSS to it:

<nav class="bg-blue-900 shadow-lg">   <div class="container mx-auto">     <div class="sm:flex">       <a href="#" class="text-white text-3xl font-bold p-3">APP LOGO</a>              <!-- Menus -->       <div class="ml-55 mt-4">         <ul class="text-white sm:self-center text-xl">           <li class="sm:inline-block">             <a href="#" class="p-3 hover:text-red-900">About</a>           </li>           <li class="sm:inline-block">             <a href="#" class="p-3 hover:text-red-900">Services</a>           </li>           <li class="sm:inline-block">             <a href="#" class="p-3 hover:text-red-900">Blog</a>           </li>           <li class="sm:inline-block">             <a href="#" class="p-3 hover:text-red-900">Contact</a>           </li>         </ul>       </div>      </div>   </div> </nav>

OK, let’s break down all those classes we just added to the HTML. First, let’s look at the <nav> element:

<nav class="bg-blue-900 shadow-lg">

We apply the class bg-blue-900 gives our header a blue background with a shade of 900, which is dark. The class shadow-lg class applies a large outer box shadow. The shadow effect this class creates will be 0px at the top, 10px on the right, 15px at the bottom, and -3px on the left.

Next is the first div, our container for the logo and navigation links:

<div class="container mx-auto">

To center it and our navigation links, we use the mx-auto class. It’s equivalent to margin: auto, horizontally centering an element within its container.

Onto the next div:

<div class="sm:flex">

By default, a div is a block-level element. We use the sm:flex class to make our header a block-level flex container, so as to make its children responsive (to enable them shrink and expand easily). We use the sm prefix to ensure that the style is applied to all screen sizes (small and above).

Alright, the logo:

<a href="#" class="text-white text-3xl font-bold p-3">APP LOGO</a>

The text-white class, true to its name, make the text of the logo white. The text-3xl class sets the font size of our logo (which is configured to 1.875rem)and its line height (configured to 2.25rem). From there, p-3 sets a padding of 0.75rem on all sides of the logo.

That takes us to:

<div class="ml-55 mt-4">

We’re giving the navigation links a left margin of 55% to move them to the right. However, there’s no Tailwind class for this, so we’ve created a custom style called ml-55, a name that’s totally made up but stands for “margin-left 55%.”

It’s one thing to name a custom class. We also have to add it to our style tags:

.ml-55 {   margin-left: 55%; }

There’s one more class in there: mt-4. Can you guess what it does? If you guessed that it seta a top margin, then you are correct! In this case, it’s configured to 1rem for our navigation links.

Next up, the navigation links are wrapped in an unordered list tag that contains a few classes:

<ul class="text-white sm:self-center text-xl">

We’re using the text-white class again, followed by sm:self-center to center the list—again, we use the sm prefix to ensure that the style is applied to all screen sizes (small and above). Then there’s text-xl which is the extra-large configured font size.

For each list item:

<li class="sm:inline-block">

The sm:inline-block class sets each list item as an inline block-level element, bringing them side-by-side.

And, lastly, the link inside each list item:

<a href="#" class="p-3 hover:text-red-900">

We use the utility class hover:text-red-900 to make each red on hover.

Let’s run our app in the command line:

npm run dev 

This is what we should get:

And that is how we used Tailwind CSS with Svelte in six little steps!

Conclusion

My hope is that you now know how to integrate Tailwind CSS into our Svelte app and configure it. We covered some pretty basic styling, but there’s always more to learn! Here’s an idea: Try improving the project we worked on by adding a sign-up form and a footer to the page. Tailwind provides comprehensive documentation on all its utility classes. Go through it and familiarize yourself with the classes.

Do you learn better with video? Here are a couple of excellent videos that also go into the process of integrating Tailwind CSS with Svelte.


The post How to Use Tailwind on a Svelte Site appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Svelte and Spring Animations

Spring animations are a wonderful way to make UI interactions come to life. Rather than merely changing a property at a constant rate over a period of time, springs allow us to move things using spring physics, which gives the impression of a real thing moving, and can appear more natural to users.

I’ve written about spring animations previously. That post was based on React, using react-spring for the animations. This post will explore similar ideas in Svelte.

CSS devs! It’s common to think of easing when it comes to controling the feel of animations. You could think of “spring” animations as a subcategory of easing that are based on real-world physics.

Svelte actually has springs built into the framework, without needing any external libraries. We’ll rehash what was covered in the first half my previous post on react-spring. But after that, we’ll take a deep-dive into all the ways these springs can be used with Svelte, and leave the real world implementation for a future post. While that may seem disappointing, Svelte has a number of wonderful, unique features with no counterpart in React, which can be effectively integrated with these animation primitives. We’re going to spend some time talking about them.

One other note: Some of the demos sprinkled throughout may look odd because I configured the springs to be extra “bouncy” to create more obvious effect. If you the code for any of them, be sure to find a spring configuration that works for you.

Here’s a wonderful REPL Rich Harris made to show all the various spring configurations, and how they behave.

A quick primer on Svelte Stores

Before we start, let’s take a very, very quick tour of Svelte stores. While Svelte’s components are more than capable of storing and updating state, Svelte also has the concept of a store, which allows you to store state outside of a component. Since Svelte’s Spring API uses Stores, we’ll quickly introduce the salient parts here.

To create an instance of a store, we can import the writable type, and create it like so:

import { writable } from "svelte/store"; const clicks = writable(0);

The clicks variable is a store that has a value of 0. There’s two ways to set a new value of a store: the set and update methods. The former receives the value to which you’re setting the store, while the latter receives a callback, accepting the current value, and returning the new value.

function increment() {   clicks.update(val => val + 1); } function setTo5() {   clicks.set(5); }

State is useless if you can’t actually consume it. For this, stores offer a subscribe method, which allows you to be notified of new values — but when using it inside of a component, you can prefix the store’s name with the $ character, which tells Svelte to not only display the current value of the store, but to update when it changes. For example:

<h1>Value {$  clicks}</h1> <button on:click={increment}>Increment</button> <button on:click={setTo5}>Set to 5</button>

Here’s a full, working example of this code. Stores offer a number of other features, such as derived stores, which allow you to chain stores together, readable stores, and even the ability to be notified when a store is first observed, and when it no longer has observers. But for the purposes of this post, the code shown above is all we need to worry about. Consult the Svelte docs or interactive tutorial for more info.

A crash course on springs

Let’s walk through a quick introduction of springs, and what they accomplish. We’ll take a look at a simple UI that changes a presentational aspect of some elements — opacity and transform — and then look at animating that change.

This is a minimal Svelte component that toggles the opacity of one <div>, and toggles the x-axis transform of another (without any animation).

<script>   let shown = true;   let moved = 0;    const toggleShow = () => (shown = !shown);   const toggleMove = () => (moved = moved ? 0 : 500); </script>  <div style="opacity: {shown ? 1 : 0}">Content to toggle</div> <br /> <button on:click={toggleShow}>Toggle</button> <hr /> <div class="box" style="transform: translateX({moved}px)">I'm a box.</div> <br /> <button on:click={toggleMove}>Move it!</button>

These changes are applied instantly, so let’s look at animating them. This is where springs come in. In Svelte, a spring is a store that we set the desired value on, but instead of instantly changing, the store internally uses spring physics to gradually change the value. We can then bind our UI to this changing value, to get a nice animation. Let’s see it in action.

<script>   import { spring } from "svelte/motion";    const fadeSpring = spring(1, { stiffness: 0.1, damping: 0.5 });   const transformSpring = spring(0, { stiffness: 0.2, damping: 0.1 });    const toggleFade = () => fadeSpring.update(val => (val ? 0 : 1));   const toggleTransform = () => transformSpring.update(val => (val ? 0 : 500));   const snapTransform = () => transformSpring.update(val => val, { hard: true }); </script>  <div style="opacity: {$  fadeSpring}">Content to fade</div> <br /> <button on:click={toggleFade}>Fade Toggle</button>  <hr />  <div class="box" style="transform: translateX({$  transformSpring}px)">I'm a box.</div> <br /> <button on:click={toggleTransform}>Move it!</button> <button on:click={snapTransform}>Snap into place</button>

We get our spring function from Svelte, and set up different spring instances for our opacity, and transform animations. The transform spring config is purposefully set up to be extra springy, to help show later how we can temporarily turn off spring animations, and instantly apply desired changes (which will come in handy later). At the end of the script block are our click handlers for setting the desired properties. Then, in the HTML, we bind our changing values directly to our elements… and that’s it! That’s all there is to basic spring animations in Svelte.

The only remaining item is the snapTransform function, where we set our transform spring to its current value, but also pass an object as the second argument, with hard: true. This has the effect of immediately applying the desired value with no animation at all.

This demo, as well as the rest of the basic examples we’ll look at in this post, is here:

Animating height

Animating height is trickier than other CSS properties, since we have to know the actual height to which we’re animating. Sadly, we can’t animate to a value of auto. That wouldn’t make sense for a spring, since the spring needs a real number so it can interpolate the correct values via spring physics. And as it happens, you can’t even animate auto height with regular CSS transitions. Fortunately, the web platform gives us a handy tool for getting the height of an element: a ResizeObserver, which enjoys pretty good support among browsers.

Let’s start with a raw height animation of an element, producing a “slide down” effect that we gradually refine in other examples. We’ll be using ResizeObserver to bind to an element’s height. I should note that Svelte does have an offsetHeight binding that can be used to more directly bind an element’s height, but it’s implemented with some <iframe> hacks that cause it to only work on elements that can receive children. This would probably be good enough for most use cases, but I’ll use a ResizeObserver because it allows some nice abstractions in the end.

First, we’ll bind an element’s height. It’ll receive the element and return a writable store that initializes a ResizeObserver, which updates the height value on change. Here’s what that looks like:

export default function syncHeight(el) {   return writable(null, (set) => {     if (!el) {       return;     }     let ro = new ResizeObserver(() => el && set(el.offsetHeight));     ro.observe(el);     return () => ro.disconnect();   }); }

We’re starting the store with a value of null, which we’ll interpret as “haven’t measured yet.” The second argument to writable is called by Svelte when the store becomes active, which it will be as soon as it’s used in a component. This is when we fire up the ResizeObserver and start observing the element. Then, we return a cleanup function, which Svelte calls for us when the store is no longer being used anywhere.

Let’s see this in action:

<script>   import syncHeight from "../syncHeight";   import { spring } from "svelte/motion";    let el;   let shown = false;   let open = false;   let secondParagraph = false;    const heightSpring = spring(0, { stiffness: 0.1, damping: 0.3 });   $  : heightStore = syncHeight(el);   $  : heightSpring.set(open ? $  heightStore || 0 : 0);    const toggleOpen = () => (open = !open);   const toggleSecondParagraph = () => (secondParagraph = !secondParagraph); </script>  <button on:click={ toggleOpen }>Toggle</button> <button on:click={ toggleSecondParagraph }>Toggle More</button> <div style="overflow: hidden; height: { $  heightSpring }px">   <div bind:this={el}>     <div>...</div>     <br />     {#if secondParagraph}     <div>...</div>     {/if}   </div> </div>

Our el variable holds the element we’re animating. We tell Svelte to set it to the DOM element via bind:this={el}. heightSpring is our spring that holds the height value of the element when it’s open, and zero when it’s closed. Our heightStore is what keeps it up to date with the element’s current height. el is initially undefined, and syncHeight returns a junk writable store that basically does nothing. As soon as el is assigned to the <div> node, that line will re-fire — thanks to the $ : syntax — and get our writable store with the ResizeObserver listening.

Then, this line:

$  : heightSpring.set(open ? $  heightStore || 0 : 0);

…listens for changes to the open value, and also changes to the height value. In either case, it updates our spring store. We bind the height in HTML, and we’re done!

Be sure to remember to set overflow to hidden on this outer element so the contents are properly clipped as the elements toggles between its opened and closed states. Also, changes to the element’s height also animate into place, which you can see with the “Toggle More” button. You can run this in the embedded demo in the previous section.

Note that this line above:

$  : heightStore = syncHeight(el);

…currently causes an error when using server-side rendering (SSR), as explained in this bug. If you’re not using SSR you don’t need to worry about it, and of course by the time you read this that bug may have been fixed. But the workaround is to merely do this:

let heightStore; $  : heightStore = syncHeight(el);

…which works but is hardly ideal.

We probably don’t want the <div> to spring open on first render. Also, the opening spring effect is nice, but when closing, the effect is janky due to some content flickering. We can fix that. To prevent our initial render from animating, we can use the { hard: true } option we saw earlier. Let’s change our call to heightSpring.set to this:

$  : heightSpring.set(open ? $  heightStore || 0 : 0, getConfig($  heightStore));

…and then see about writing a getConfig function that returns an object with the hard property that was set to true for the first render. Here’s what I came up with:

let shown = false;  const getConfig = val => {   let active = typeof val === "number";   let immediate = !shown && active;   //once we've had a proper height registered, we can animate in the future   shown = shown || active;   return immediate ? { hard: true } : {}; };

Remember, our height store initially holds null and only gets a number when the ResizeObserver starts running. We capitalize on this by checking for an actual number. If we have a number, and we haven’t yet shown anything, then we know to show our content immediately, and we we do that by setting the immediate value. That value ultimately triggers the hard config value in the spring, which we saw before.

Now let’s tweak the animation to be a bit less, well, springy when we close our content. That way, things won’t flicker when they close. When we initially created our spring, we specified stiffness and damping, like so

const heightSpring = spring(0, { stiffness: 0.1, damping: 0.3 });

It turns out the spring object itself maintains those properties, which can be set anytime. Let’s update this line:

$  : heightSpring.set(open ? $  heightStore || 0 : 0, getConfig($  heightStore));

That detects changes to the open value (and the heightStore itself) to update the spring. Let’s also update the spring’s settings based on whether we’re opening or closing. Here’s what it looks like:

$  : {   heightSpring.set(open ? $  heightStore || 0 : 0, getConfig($  heightStore));   Object.assign(     heightSpring,     open ? { stiffness: 0.1, damping: 0.3 } : { stiffness: 0.1, damping: 0.5 }   ); }

Now when we get a new open or height value, we call heightSpring.set just like before, but we also set stiffness and damping values on the spring that are applied based on whether the element is open. If it’s closed, we set damping up to 0.5, which reduces the springiness. Of course, you’re welcome to tweak all these values and configure them as you’d like! You can see this in the “Animate Height Different Springs” section of the demo.

You might notice our code is starting to grow pretty quickly. We’ve added a lot of boilerplate to cover some of these use cases, so let’s clean things up. Specifically, we’ll make a function that creates our spring and that also exports a sync function to handle our spring config, initial render, etc.

import { spring } from "svelte/motion";  const OPEN_SPRING = { stiffness: 0.1, damping: 0.3 }; const CLOSE_SPRING = { stiffness: 0.1, damping: 0.5 };  export default function getHeightSpring() {   const heightSpring = spring(0);   let shown = false;    const getConfig = (open, val) => {     let active = typeof val === "number";     let immediate = open && !shown && active;     // once we've had a proper height registered, we can animate in the future     shown = shown || active;     return immediate ? { hard: true } : {};   };    const sync = (open, height) => {     heightSpring.set(open ? height || 0 : 0, getConfig(open, height));     Object.assign(heightSpring, open ? OPEN_SPRING : CLOSE_SPRING);   };    return { sync, heightSpring }; }

There’s a lot of code here, but it’s all the code we’ve been writing so far, just packaged into a single function. Now our code to use this animation is simplified to just this

const { heightSpring, sync } = getHeightSpring(); $  : heightStore = syncHeight(el); $  : sync(open, $  heightStore);

You can see in the “Animate Height Cleanup” section of the demo.

Some Svelte-specific tricks

Let’s pause for a moment and consider some ways Svelte differs from React, and how we might leverage that to improve what we have even further.

First, the stores we’ve been using to hold springs and change height values are, unlike React’s hooks, not tied to component rendering. They’re plain JavaScript objects that can be consumed anywhere. And, as alluded to above, we can imperatively subscribe to them so that they manually observe changing values.

Svelte also something called actions. These are functions that can be added to a DOM element. When the element is created, Svelte calls the function and passes the element as the first argument. We can also specify additional arguments for Svelte to pass, and provide an update function for Svelte to re-run when those values change. Another thing we can do is provide a cleanup function for Svelte to call when it destroys the element.

Let’s put these tools together in a single action that we can simply drop onto an element to handle all the animation we’ve been writing so far:

export default function slideAnimate(el, open) {   el.parentNode.style.overflow = "hidden";    const { heightSpring, sync } = getHeightSpring();   const doUpdate = () => sync(open, el.offsetHeight);   const ro = new ResizeObserver(doUpdate);    const springCleanup = heightSpring.subscribe((height) => {     el.parentNode.style.height = `$  { height }px`;   });    ro.observe(el);    return {     update(isOpen) {       open = isOpen;       doUpdate();     },     destroy() {       ro.disconnect();       springCleanup();     }   }; }

Our function is called with the element we want to animate, as well as the open value. We’ll set the element’s parent to have overflow: hidden. Then we use the same getHeightSpring function from before, set up our ResizeObserver, etc. The real magic is here.

const springCleanup = heightSpring.subscribe((height) => {   el.parentNode.style.height = `$  {height}px`; });

Instead of binding our heightSpring to the DOM, we manually subscribe to changes, then set the height ourselves, manually. We wouldn’t normally do manual DOM updates when using a JavaScript framework like Svelte but, in this case, it’s for a helper library, which is just fine in my opinion.

In the object we’re returning, we define an update function which Svelte will call when the open value changes. We update the original argument to this function, which the function closes over ( i.e. creates a closure around) and then calls our update function to sync everything. Svelte calls the destroy function when our DOM node is destroyed.

Best of all, using this action is a snap:

<div use:slideAnimate={open}>

That’s it. When open changes, Svelte calls our update function.

Before we move on, let’s make one other tweak. Notice how we remove the springiness by changing the spring config when we collapse the pane with the “Toggle” button; however, when we make the element smaller by clicking the “Toggle More” button, it shrinks with the usual springiness. I dislike that, and prefer shrinking sizes move with the same physics we’re using for collapsing.

Let’s start by removing this line in the getHeightSpring function:

Object.assign(heightSpring, open ? OPEN_SPRING : CLOSE_SPRING);

That line is inside the sync function that getHeightSpring created, which updates our spring settings on every change, based on the open value. With it gone, we can start our spring with the “open” spring config:

const heightSpring = spring(0, OPEN_SPRING);

Now let’s change our spring settings when either the height of our content changes, or when the open value changes. We already have the ability to observe both of those things changing — our ResizeObserver callback fires when the size of the content changes, and the update function of our action fires whenever open changes.

Our ResizeObserver callback can be changed, like this:

let currentHeight = null; const ro = new ResizeObserver(() => {   const newHeight = el.offsetHeight;   const bigger = newHeight > currentHeight;    if (typeof currentHeight === "number") {     Object.assign(heightSpring, bigger ? OPEN_SPRING : CLOSE_SPRING);   }   currentHeight = newHeight;   doUpdate(); });

currentHeight holds the current value, and we check it on size changes to see which direction we’re moving. Next up is the update function. Here’s what it looks like after our change:

update(isOpen) {   open = isOpen;   Object.assign(heightSpring, open ? OPEN_SPRING : CLOSE_SPRING);   doUpdate(); },

Same idea, but now we’re only checking whether open is true or false. You can see these iterations in the “Slide Animate” and “Slide Animate 2” sections of the demo.

Transitions

We’ve talked about animating items already on the page so far, but what about animating an object when it first renders? And when it un-mounts? That’s called a transition, and it’s built into Svelte. The docs do a superb job covering the common use cases, but there’s one thing that’s not yet (directly) supported: spring-based transitions.

/explanation Note that what Svelte calls a “transition” and what CSS calls a “transition” are very different things. CSS means transitioning one value to another. Svelte is referring to elements as they “transition” into and out of the DOM entirely (something that CSS doesn’t help with much at all).

To be clear, the work we’re doing here is made for adding spring-based animations into Svelte’s transitions. This is not currently supported, so it requires some tricks and workarounds that we’ll get into. If you don’t care about using springs, then Svelte’s built-in transitions can be used, which are significantly simpler. Again, check the docs for more info.

The way transitions work in Svelte is that we provide a duration in milliseconds (ms) along with an optional easing function, then Svelte provides us a callback with a value running from 0 to 1, representing how far along the transition is, and we turn that into whatever CSS we want. For example:

const animateIn = () => {   return {     duration: 2000,     css: t => `transform: translateY($  {t * 50 - 50}px)`   }; };

…is used like this:

<div in:animateIn out:animateOut class="box">   Hello World! </div>

When that <div> first mounts, Svelte:

  • calls our animateIn function,
  • rapidly calls the CSS function on our resulting object ahead of time with values from 0 to 1,
  • collects our changing CSS result, then
  • compiles those results into a CSS keyframes animation, which it then applies to the incoming <div>.

This means that our animation will run as a CSS animation — not as JavaScript on the main thread — offering a nice performance boost for free.

The variable t starts at 0, which results in a translation of -50px. As t gets closer to 1, the translation approaches 0, its final value. The out transition is about the same, but in reverse, with the added feature of detecting the box’s current translation value, starting from there. So, if we add it then quickly remove it, the box will start to leave from its current position rather than jumping ahead. However, if we then re-add it while it’s leaving, it will jump, something we’ll talk about in just moment.

You can run this in the “Basic Transition” section of the demo.

Transitions, but with springs

While there’s a number of easing functions that alter the flow of an animation, there’s no ability to directly use springs. But what we could do is find some way to run a spring ahead of time, collect the resulting values, and then, when our css function is called with the a t value running from 0 to 1, look up the right spring value. So, if t is 0, we obviously need the first value from thespring. When t is 0.5, we want the value right in the middle, and so on. We also need a duration, which is number_of_spring_values * 1000 / 60 since there’s 60 frames per second.

We won’t write that code here. Instead, we’ll use the solution that already exists in the svelte-helpers library, a project I started. I grabbed one small function from the Svelte codebase, spring_tick, then wrote a separate function to repeatedly call it until it’s finished, collecting the values along the way. That, along with a translation from t to the correct element in that array (or a weighted average if there’s not a direct match), is all we need. Rich Harris gave a helping hand on the latter, for which I’m grateful.

Animate in

Let’s pretend a big red <div> is a modal that we want to animate in, and out. Here’s what an animateIn function looks like:

import { springIn, springOut } from "svelte-helpers/animation"; const SPRING_IN = { stiffness: 0.1, damping: 0.1 };  const animateIn = node => {   const { duration, tickToValue } = springIn(-80, 0, SPRING_IN);   return {     duration,     css: t => `transform: translateY($  { tickToValue(t) }px)`   }; };

We feed the values we want to spring to, as well as our spring config to the springIn function. That gives us a duration, and a function for translating the current tickToValue into the current value to apply in the CSS. That’s it!

Animate out

Closing the modal is the same thing, with one small tweak

const SPRING_OUT = { stiffness: 0.1, damping: 0.5, precision: 3 };  const animateOut = node => {   const current = currentYTranslation(node);   const { duration, tickToValue } = springOut(current ? current : 0, 80, SPRING_OUT);   return {     duration: duration,     css: t => `transform: translateY($  { tickToValue(t) }px)`   }; };

Here, we’re check the modal’s current translation position, then use that as a starting point for the animation. This way, if the user opens and then quickly closes the modal, it’ll exit from its current position, rather than teleporting to 0, and then leaving. This works because the animateOut function is called when the element un-mounts, at which point we generate the object with the duration property and css function so the animation can be computed.

Sadly, it seems re-mounting the object while it’s in the process of leaving does not work, at least well. The animateIn function is not called de novo, but rather the original animation is re-used, which means it’ll always start at -80. Fortunately this almost certainly would not matter for a typical modal component, since a modal is usually removed by clicking on something, like the background overlay, meaning we are unable to re-show it until that overlay has finished animating out. Besides, repeatedly adding and removing an element with bidirectional transitions might make for a fun demo, but they’re not really common in practice, at least in my experience.

One last quick note on the outgoing spring config: You may have noticed that I set the precision ridiculously high (3 when the default is 0.01). This tells Svelte how close to get to the target value before deciding it is “done.” If you leave the default at 0.01, the modal will (almost) hit its destination, then spend quite a few milliseconds imperceptibly getting closer and closer before deciding it’s done, then remove itself from the DOM. This gives the impression that the modal is stuck, or otherwise delayed. Moving the precision to a value of 3 fixes this. Now the modal animates to where it should go (or close enough), then quickly goes away.

More animation

Let’s add one final tweak to our modal example. Let’s have it fade in and out while animating. We can’t use springs for this, since, again, we need to have one canonical duration for the transition, and our motion spring is already providing that. But spring animations usually make sense for items actually moving, and not much else. So let’s use an easing function to create a fade animation.

If you need help picking the right easing function, be sure to check out this handy visualization from the Svelte docs. I’ll be using the quintOut and quadIn functions.

import { quintOut, quadIn } from "svelte/easing";

Our new animateIn function looks pretty similar. Our css function does what it did before, but also runs the tickToValue value through the quintOut easing function to get our opacity value. Since t runs from 0 to 1 during an in transition, and 1 to 0 during an out transition, we don’t have to do anything further to it before applying to opacity.

const SPRING_IN = { stiffness: 0.1, damping: 0.1 }; const animateIn = node =>; {   const { duration, tickToValue } = springIn(-80, 0, SPRING_IN);   return {     duration,     css: t => {       const transform = tickToValue(t);       const opacity = quintOut(t);       return `transform: translateY($  { transform }px); opacity: $  { opacity };`;     }   }; };

Our animateOut function is similar, except we want to grab the element’s current opacity value, and force the animation to start there. So, if the element is in the process of fading in, with an opacity of, say, 0.3, we don’t want to reset it to 1, and then fade it out. Instead, we want to fade it out from 0.3.

Multiplying that starting opacity by whatever value the easing function returns accomplishes this. If our t value starts at 1, then 1 * 0.3 is 0.3. If t is 0.95, we do 0.95 * 0.3 to get a value, which is a little less than 0.3, and so on.

Here’s the function:

const animateOut = node => {   const currentT = currentYTranslation(node);   const startOpacity = +getComputedStyle(node).opacity;   const { duration, tickToValue } = springOut(     currentT ? currentT : 0,     80,     SPRING_OUT   );   return {     duration,     css: t => {       const transform = tickToValue(t);       const opacity = quadIn(t);       return `transform: translateY($  { transform }px); opacity: $  { startOpacity * opacity }`;     }   }; }; 

You can run this example in the demo with the “Spring Transition With Fade component.

Parting thoughts

Svelte is a lot of fun! In my (admittedly limited) experience, it tends to provide extremely simple primitives, and then leaves you to code up whatever you need. I hope this post has helped explain how the spring animations can be put to good use in your web applications.

And, hey, just a quick reminder to consider accessibility when working with springs, just as you would do with any other animation. Pairing these techniques with something like prefers-reduced-motion can ensure that only folks who prefer animations are the ones who get them.


The post Svelte and Spring Animations appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Integrating TypeScript with Svelte

Svelte is one of the newer JavaScript frameworks and it’s rapidly rising in popularity. It’s a template-based framework, but one which allows for arbitrary JavaScript inside the template bindings; it has a superb reactivity story that’s simple, flexible and effective; and as an ahead-of-time (AOT) compiled framework, it has incredibly impressive perf, and bundle sizes. This post will focus on configuring TypeScript inside of Svelte templates. If you’re new to Svelte, I’d urge you to check out the introductory tutorial and docs.

If you’d like to follow along with the code (or you want to debug what you might be missing in your own project) you can clone the repo. I have branches set up to demonstrate the various pieces I’ll be going over.

Basic TypeScript and Svelte setup

Let’s look at a baseline setup. If you go to the initial-setup branch in the repo, there’s a bare Svelte project set up, with TypeScript. To be clear, TypeScript is only working in stand-alone .ts files. It’s not in any way integrated into Svelte. Accomplishing the TypeScript integration is the purpose of this post.

I’ll go over a few pieces that make Svelte and TypeScript work, mainly since I’ll be changing them in a bit, to add TypeScript support to Svelte templates.

First, I have a tsconfig.json file:

{   "compilerOptions": {     "module": "esNext",     "target": "esnext",     "moduleResolution": "node"   },   "exclude": ["./node_modules"] }

This file tells TypeScript that I want to use modern JavaScript, use Node resolution, and exclude a node_modules from compilation.

Then, in typings/index.d.ts I have this:

declare module "*.svelte" {   const value: any;   export default value; }

This allows TypeScript to co-exist with Svelte. Without this, TypeScript would issue errors any time a Svelte file is loaded with an import statement. Lastly, we need to tell webpack to process our Svelte files, which we do with this rule in webpack.config.js:

{   test: /.(html|svelte)$ /,   use: [     { loader: "babel-loader" },     {       loader: "svelte-loader",       options: {         emitCss: true,       },     },   ], }

All of that is the basic setup for a project using Svelte components and TypeScript files. To confirm everything builds, open up a couple of terminals and run npm start in one, which will start a webpack watch, and npm run tscw in the other, to start a TypeScript watch task. Hopefully both will run without error. To really verify the TypeScript checking is running, you can change:

let x: number = 12;

…in index.ts to:

let x: number = "12";

…and see the error come up in the TypeScript watch. If you want to actually run this, you can run node server in a third terminal (I recommend iTerm2, which allows you to run these terminals inside tabs in the same window) and then hit localhost:3001.

Adding TypeScript to Svelte

Let’s add TypeScript directly to our Svelte component, then see what configuration changes we need to make it work. First go to Helper.svelte, and add lang="ts" to the script tag. That tells Svelte there’s TypeScript inside the script. Now let’s actually add some TypeScript. Let’s change the val prop to be checked as a number, via export let val: number;. The whole component now looks like this:

<script lang="ts">   export let val: number; </script>  <h1>Value is: {val}</h1>

Our webpack window should now have an error, but that’s expected.

Showing the terminal with an error output.

We need to tell the Svelte loader how to handle TypeScript. Let’s install the following:

npm i svelte-preprocess svelte-check --save

Now, let’s go to our webpack config file and grab svelte-preprocess:

const sveltePreprocess = require("svelte-preprocess");

…and add it to our svelte-loader:

{   test: /.(html|svelte)$ /,   use: [     { loader: "babel-loader" },     {       loader: "svelte-loader",       options: {         emitCss: true,         preprocess: sveltePreprocess({})       },     },   ], }

OK, let’s restart the webpack process, and it should build.

Add checking

So far, what we have builds, but it doesn’t check. If we have invalid code in a Svelte component, we want that to generate an error. So, let’s go to App.svelte, add the same lang="ts" to the script tag, and then pass an invalid value for the val prop, like this:

<Helper val={"3"} />

If we look in our TypeScript window, there are no errors, but there should be. It turns out we don’t type check our Svelte template with the normal tsc compiler, but with the svelte-check utility we installed earlier. Let’s stop our TypeScript watch and, in that terminal, run npm run svelte-check. That’ll start the svelte-check process in watch mode, and we should see the error we were expecting.

Showing the terminal with a caught error.

Now, remove the quotes around the 3, and the error should go away:

Showing the same terminal window, but no errors.

Neat!

In practice, we’d want both svelte-check and tsc running at the same time so we catch both errors in both our TypeScript files and Svelte templates. There’s a bunch of utilities on npm that allow can do this, or we can use iTerm2, is able to split multiple terminals in the same window. I’m using it here to run the server, webpack build, tsc build, and svelte-check build.

Showing iTerm2 window with four open terminals in a two-by-two grid.

This setup is in the basic-checking branch of the repo.

Catching missing props

There’s still one problem we need to solve. If we omit a required prop, like the val prop we just looked at, we still won’t get an error, but we should, since we didn’t assign it a default value in Helper.svelte, and is therefore required.

<Helper /> // missing `val` prop

To tell TypeScript to report this as an error, let’s go back to our tsconfig, and add two new values

"strict": true, "noImplicitAny": false 

The first enables a bunch of TypeScript checks that are disabled by default. The second, noImplicitAny, turns off one of those strict checks. Without that second line, any variable lacking a type—which is implicitly typed as any—is now reported as an error (no implicit any, get it?)

Opinions differ widely on whether noImplicitAny should be set to true. I happen to think it’s too strict, but plenty of people disagree. Experiment and come to your own conclusion.

Anyway, with that new configuration in place, we should be able to restart our svelte-check task and see the error we were expecting.

Showing terminal with a caught error.

This setup is in the better-checking branch of the repo.

Odds and ends

One thing to be aware of is that TypeScript’s mechanism for catching incorrect properties is immediately, and irreversibly switched off for a component if that component ever references $ $ props or $ $ restProps. For example, if you were to pass an undeclared prop of, say, junk into the Helper component, you’d get an error, as expected, since that component has no junk property. But this error would immediately go away if the Helper component referenced $ $ props or $ $ restProps. The former allows you to dynamically access any prop without having an explicit declaration for it, while $ $ restProps is for dynamically accessing undeclared props.

This makes sense when you think about it. The purpose of these constructs is to dynamically access a property on the fly, usually for some sort of meta-programming, or to arbitrarily pass attributes on to an html element, which is common in UI libraries. The existence of either of them implies arbitrary access to a component that may not have been declared.

There’s one other common use of $ $ props, and that’s to access props declared as a reserved word. class is a common example of this. For example:

const className = $ $ props.class;

…since:

export let class = "";

…is not valid. class is a reserved word in JavaScript but there’s a workaround in this specific case. The following is also a valid way to declare that same prop—thanks to Rich Harris for helping with this.

let className; export { className as class };

If your only use of $ $ props is to access a prop whose name is reserved, you can use this alternative, and maintain better type checking for your component.

Parting thoughts

Svelte is one of the most promising, productive, and frankly fun JavaScript frameworks I’ve worked with. The relative ease with which TypeScript can be added is like a cherry on top. Having TypeScript catch errors early for you can be a real productivity boost. Hopefully this post was of some help achieving that.


The post Integrating TypeScript with Svelte appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Lazy Loading Images in Svelte

One easy way to improve the speed of a website is to only download images only when they’re needed, which would be when they enter the viewport. This “lazy loading” technique has been around a while and there are lots of great tutorials on how to implement it.

But even with all the resources out there, implementing lazy loading can look different depending on the project you’re working in or the framework you’re using. In this article, I’ll use the Intersection Observer API alongside the onLoad event to lazy load images with the Svelte JavaScript framework.

Check out Tristram Tolliday’s introduction to Svelte if you’re new to the framework.

Let’s work with a real-life example

I put this approach together while testing the speed on a Svelte and Sapper application I work on, Shop Ireland. One of our goals is to make the thing as fast as we possible can. We hit a point where the homepage was taking a performance hit because the browser was downloading a bunch of images that weren’t even on the screen, so naturally, we turned to lazy loading them instead.

Svelte is already pretty darn fast because all of the code is compiled in advance. But once we tossed in lazy loading for images, things really started speeding up.

This is what we’re going to work on tofgether. Feel free to grab the final code for this demo from GitHub and read along for an explanation of how it works.

This is where we’ll end up by the end:

Let’s quickly start up Svelte

You might already have a Svelte app you’d like to use, but if not, let’s start a new Svelte project and work on it locally. From the command line:

npx degit sveltejs/template my-svelte-project cd my-svelte-project npm install npm run dev

You should now have a beginner app running on http://localhost:5000.

Adding the components folder

The initial Svelte demo has an App.svelte file but no components just yet. Let’s set up the components we need for this demo. There is no components  folder, so let’s create one in the src folder. Inside that folder, create an Image folder — this will hold our components for this demo.

We’re going to have our components do two things. First, they will check when an image enters the viewport. Then, when an image does enter, the components will wait until the image file has loaded before showing it.

The first component will be an <IntersectionObserver> that wraps around the second component, an <ImageLoader>. What I like about this setup is that it allows each component to be focused on doing one thing instead of trying to pack a bunch of operations in a single component.

Let’s start with the <IntersectionObserver> component.

Observing the intersection

Our first component is going to be a working implementation of the Intersection Observer API. The Intersection Observer is a pretty complex thing but the gist of it is that it watches a child element and informs us when it enters the bounding box of its parent. Hence images: they can be children of some parent element and we can get a heads up when they scroll into view.

While it’s definitely a great idea to get acquainted with the ins and outs of the Intersection Observer API — and Travis Almand has an excellent write-up of it — we’re going to make use of a handy Svelte component that Rich Harris put together for svelte.dev.

We’ll set this up  first before digging into what exactly it does. Create a new IntersectionObserver.svelte file and drop it into the src/components/Image folder. This is where we’ll define the component with the following code:

<script>   import { onMount } from 'svelte'; 
   export let once = false;   export let top = 0;   export let bottom = 0;   export let left = 0;   export let right = 0; 
   let intersecting = false;   let container; 
   onMount(() => {     if (typeof IntersectionObserver !== 'undefined') {       const rootMargin = `$  {bottom}px $  {left}px $  {top}px $  {right}px`; 
       const observer = new IntersectionObserver(entries => {         intersecting = entries[0].isIntersecting;         if (intersecting && once) {           observer.unobserve(container);         }       }, {         rootMargin       }); 
       observer.observe(container);       return () => observer.unobserve(container);     } 
     function handler() {       const bcr = container.getBoundingClientRect(); 
       intersecting = (         (bcr.bottom + bottom) > 0 &&         (bcr.right + right) > 0 &&         (bcr.top - top) < window.innerHeight &&         (bcr.left - left) < window.innerWidth       ); 
       if (intersecting && once) {         window.removeEventListener('scroll', handler);       }     } 
     window.addEventListener('scroll', handler);     return () => window.removeEventListener('scroll', handler);   }); </script> 
 <style>   div {     width: 100%;     height: 100%;   } </style> 
 <div bind:this={container}>   <slot {intersecting}></slot> </div>

We can use this component as a wrapper around other components, and it will determine for us whether the wrapped component is intersecting with the viewport.

If you’re familiar with the structure of Svelte components, you’ll see it follows a pattern that starts with scripts, goes into styles, then ends with markup. It sets some options that we can pass in, including a once property, along with numeric values for the top, right, bottom and left distances from the edge of the screen that define the point where the intersection begins.

We’ll ignore the distances but instead make use of the once property. This will ensure the images only load once, as they enter the viewport.

The main logic of the component is within the onMount section. This sets up our observer, which is used to check our element to determine if it’s “intersecting” with the visible area of the screen. It also attaches a scroll event to check whether the element is visible as we scroll, and then it’ll remove this listener if we’ve determined that it is viable and that once is true.

Loading the images

Let’s use our <IntersectionObserver> component to conditionally load images by wrapping it around an <ImageLoader> component. Again, this is the component that receives a notification from the <IntersectionOberserver> so it knows it’s time to load an image.

That means we’ll need a new component file in components/Image. Let’s call it ImageLoader.svelte. Here’s the code we want in it:

<script>   export let src   export let alt 
   import IntersectionObserver from './IntersectionObserver.svelte'   import Image from './Image.svelte'    </script> 
 <IntersectionObserver once={true} let:intersecting={intersecting}>   {#if intersecting}     <Image {alt} {src} />   {/if} </IntersectionObserver>

This component takes some image-related props — src and alt — that we will use to create the actual markup for an image. Notice that we’re importing two components in the scripts section, including the <IntersectionObserver> we just created and another one called <Image> that we haven’t created yet, but will get to in a moment.

The <IntersectionObserver> is put to work by acting as a wrapping around the soon-to-be-created <Image> component. Check out those properties on it.  We are setting once to true, so the image only loads the first time we see it.

Then we make use of Svelte’s slot props. What are those? Let’s cover that next.

Slotting property values

Wrapping component, like our <IntersectionObserver> are handy for passing props to the children it contains. Svelte gives us something called slot props to make that happen.

In our <IntersectionObserver> component you may have noticed this line:

<slot {intersecting}></slot>

This is passing the intersecting prop into whatever component we give it. In this case, our <ImageLoader> component receives the prop when it uses the wrapper. We access the prop using let:intersecting={intersecting} like so:

<IntersectionObserver once={true} let:intersecting={intersecting}>

We can then use the intersecting value to determine when it’s time to load an <Image> component. In this case, we’re using an if condition to check for when it’s go time:

<IntersectionObserver once={true} let:intersecting={intersecting}>   {#if intersecting}     <Image {alt} {src} />   {/if} </IntersectionObserver> 

If the intersection is happening, the <Image> is loaded and receives the alt and src props. You can learn a bit more about slot props in this Svelte tutorial.

We now have the code in place to show an <Image> component when it is scrolled onto the screen. Let’s finally get to building the component.

Showing images on load

Yep, you guessed it: let’s add an Image.svelte file to the components/Image folder for our <Image> component. This is the component that receives our alt and src props and sets them on an <img> element.

Here’s the component code:

<script>   export let src   export let alt 
   import { onMount } from 'svelte' 
   let loaded = false   let thisImage 
   onMount(() => {     thisImage.onload = () => {       loaded = true     }   })  
 </script> 
 <style>   img {     height: 200px;     opacity: 0;     transition: opacity 1200ms ease-out;   }   img.loaded {     opacity: 1;   } </style> 
 <img {src} {alt} class:loaded bind:this={thisImage} />

Right off the bat, we’re receiving the alt and src props before defining two new variables: loaded to store whether the image has loaded or not, and thisImage to store a reference to the img DOM element itself.

We’re also using a helpful Svelte method called onMount. This gives us a way to call functions once a component has been rendered in the DOM. In this case, we’re set a callback for thisImage.onload. In plain English, that means it’s executed when the image has finished loading, and will set the loaded variable to a true value.

We’ll use CSS to reveal the image and fade it into view. Let’s give set an opacity: 0 on images so they are initially invisible, though technically on the page. Then, as they intersect the viewport and the <ImageLoader> grants permission to load the image, we’ll set the image to full opacity. We can make it a smooth transition by setting the transition property on image. The demo sets the transition time to 1200ms but you can speed it up or slow it down as needed.

That leads us to the very last line of the file, which is the markup for an <img> element.

<img {src} {alt} class:loaded bind:this={thisImage} />

This uses class:loaded to conditionally apply a .loaded class if the loaded variable is true. It also uses the bind:this method to associate this DOM element with the thisImage variable.

Let’s hook it all up!

Alright, it’s time to actually use our component. Crack open the App.svelte file and drop in the following code to import our component and use it:

<script>   import ImageLoader from './components/Image/ImageLoader.svelte'; </script> 
 <ImageLoader src="OUR_IMAGE_URL" alt="Our image"></ImageLoader>

Here’s the demo once again:

And remember that you’re welcome to download the complete code for this demo on GitHub. If you’d like to see this working on a production site, check out my Shop Ireland project. Lazy loading is used on the homepage, category pages and search pages to help speed things up. I hope you find it useful for your own Svelte projects!


The post Lazy Loading Images in Svelte appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Getting Acquainted With Svelte, the New Framework on the Block

For the last six years, Vue, Angular, and React have run the world of front-end component frameworks. Google and Facebook have their own sponsored frameworks, but they might leave a bitter taste for anyone who advocates for an open and unbiased web. Vue is another popular framework that has multiple sponsors, but isn’t run by a single corporation, which may be attractive to some folks.

There’s another player in the framework space that’s gaining attention and operates very much in the same spirit as Vue as far adopting an open MIT license: Svelte.

Svelte has been covered here on CSS-Tricks before, like Ollie Williams’ excellent overview of how it can be used to write more convenient, component-based CSS. This article is going to zoom out a bit and provide a little more context about Svelt, as well as how it differentiates itself from other frameworks, and how to implement it in your own projects.

What makes Svelte different?

I can confidently say that Svelte has been the easiest JavaScript component library to learn and start putting to use in a productive way.

— Jeff Delaney, from Svelte Realtime Todo List with Firebase

OK, so Svelte is a JavaScript component library. But so is React. And Angular. And Vue. What makes Svelte stand out from the bunch?

Svelte is trying to do a few things that are different from the rest:

  1. All the code is compiled ahead of time.
  2. There is no virtual DOM.
  3. CSS scoping is baked in.

Let’s break those down a bit because they significantly distinguish Svelte from other front-end frameworks.

All the code is compiled ahead of time.

Svelte is a compiler, meaning that the code in Svelte files gets converted from an easier-to-write hybrid language that mixes HTML, JavaScript, and CSS into lower-level optimized JavaScript, HTML, and CSS files.

This is very similar to the way C# gets compiled down to bytecode, or how Typescript compiles down to JavaScript. But where traditional compilers tend to go down to one language, Svelte mixes all three.

This makes writing code a lot more flexible, and benefits the client (web browser) as the computation is done when the application is built, not on every browser when the web app is visited.

There is no Virtual DOM.

A DOM (or Document Object Model) is an interface that defines the logical structure of a webpage. It takes HTML and converts it to a structure that can be manipulated and accessed. Chris has a classic post that thoroughly explains it.

The Virtual DOM extends the concept of a DOM by creating a “second” DOM in memory. Like the DOM, this is manipulated and accessed by traditional frameworks (e.g. Angular, Vue, and React). At build, this second “virtual” DOM gets consolidated with the actual DOM, allowing the UI to render.

And what about the Shadow DOM? Well, the Shadow DOM is technically part of the “real” DOM, just in the shadows. As such it is a great tool for isolating chunks of code that don’t leak into or conflict with other elements on the page — a little bit like (but at the same time almost nothing like) an iframe. The shadow DOM is sorta the crux for most component-based front-end frameworks because they leverage the siloed nature of the Shadow DOM to serve specific code to specific elements.

While that isn’t exactly a key selling point of Svelte, it is possible to work with the Shadow DOM experimentally. The Shadow DOM hasn’t really quite caught on in progressive web practices, which is a shame, and probably due to the confusion between drafts and lack of support from IE and Edge.

So, where am I going with all this? The difference between Svelte and other JavaScript frameworks is the lack of a Virtual DOM. That’s important because it contributes to faster apps — faster than frameworks using a Virtual DOM. Yes, the Virtual DOM can be super fast because it only updates parts of the DOM when needed, but as applications grow, the impact of a duplicate DOM stored in memory can have an overall negative impact on performance.

Svelte takes a different approach and does a lot of these heavy calculations at build time. All that heavy lifting in advance, which allows Svelte to surgically insert changes only where needed.

CSS scoping is baked in.

Svelte has built-in styling, which is essential in other modern frameworks. The different between CSS in Svelte and CSS in other frameworks is that Svelte takes the CSS from each component and spits it out to a separate CSS file on build.

A personal gripe I have with most CSS-in-JS approaches is that it seems like an over-engineered solution. Svelte’s approach keeps things lean, vanilla, and encapsulated — while keeping everything where it should be.

For those who love preprocessors, there are plugins, whether it for Sass, Less or Gulp. But since Svelte is still in its infancy, I would recommend using plain ol’ CSS with a minified CSS framework of your choice so you can utilize Svelte’s handy dandy component scoping. 

You could just as easily keep to your usual styling preferences and completely forgo Svelte’s CSS builder. However, I’d argue that is a massive shame, as Svelte’s solution has been extremely clean and enjoyable, at lease in my experience. But anyone who has to work with IE11 (😬) and even older browsers will know that normalizing styles is a must. This is a good place to stop and check out Ollie’s post because he dives much deeper into Svelte’s styling features and advantages.

How Svelte stacks up to other frameworks

We just looked at what how Svelte has a different approach for compiling, interacting with DOM and writing CSS. You might be wondering: how does Svelte compare to other popular frameworks?

There are plenty of comparisons already out there, but suffice to say that Svelte is pretty darn fast. But speed isn’t the only basis for comparison. Instead, let’s do a side-by-side that looks at a broader overview in a format much loved by the development community: a table!

Svelte Vue React Angular (2+)
What is it Compiler Framework Framework Framework
First Commit Nov. 16, 2016 Jul. 29, 2013 May 24, 2013 Sep. 18, 2014
Backing Open source Multiple Sponsors Facebook Google
Community¹ Small Large Massive Large
Satisfaction2 88% 87% 89% 38%

Svelte is in a strong position considering its late entrance and small community. Developer satisfaction is high, while the big three have been seeing recent declines. The Svelte community is small, but growing, and the code is open source which is a huge plus for the overall web community.

Let’s look at an example of using Svelte

I hope that I have convinced you that Svelte is worth at least a try. If so, let’s fire up the terminal and try a real-world examples of an everyday use case: implementing the Intersection Observer. If you’ve ever run a Lighthouse report, it may have been shouted at you for not using passive scroll events. That may be the most boring sentence I have written in my life, but it’s scores points for performance and isn’t overly complicated to do with the Intersection Observer in Svelte.

Let’s skip all the installation and setup stuff because we can avoid it with REPL, the online editor Svelte uses to demonstrate the framework on its site. The standard “Hello world” boilerplate is in there. Go ahead and download the ZIP file of the app, in the upper-right corner of the screen.

Now, unzip the file and cd into the folder from the terminal and run  npm -i to initialize the project. Once that’s done, do npm run build and you’ll get a copy of your lightweight miniature Svelte “Hello, world!” app.

Now we can get into the actual task of adding the IntersectionObserver.

First, we import the code that has already kindly been written by the Svelte team. It’s in the source code of the svelte.dev git repo (the inner cogs of which make for fascinating reading).

<script>   import { onMount } from 'svelte';   export let once = false;   export let top = 0;   export let bottom = 0;   export let left = 0;   export let right = 0;   let intersecting = false;   let container;    onMount(() => {     if (typeof IntersectionObserver !== 'undefined') {       const rootMargin = `$  {bottom}px $  {left}px $  {top}px $  {right}px`;       const observer = new IntersectionObserver(entries => {         intersecting = entries[0].isIntersecting;         if (intersecting && once) {           observer.unobserve(container);         }         }, {           rootMargin       });         observer.observe(container);         return () => observer.unobserve(container);   }    function handler() {     const bcr = container.getBoundingClientRect();     intersecting = (       (bcr.bottom + bottom) > 0 &&       (bcr.right + right) > 0 &&       (bcr.top - top) < window.innerHeight &&       (bcr.left - left) < window.innerWidth     );     if (intersecting && once) {       window.removeEventListener('scroll', handler);     }   }    window.addEventListener('scroll', handler);     return () => window.removeEventListener('scroll', handler);   }); </script>  <style>   div {     width: 100%;     height: 100%;   } </style>  <div bind:this={container}>   <slot {intersecting}></slot> </div>

Stick this in a file called IntersectionObserver.svelte in a src/components folder. Then, reference it from the main Svelte file: App.svelte.

import IntersectionObserver from "../components/IntersectionObserver.svelte";

Now that we have the Intersection Observer available as a component, we can wrap other elements with it.

<IntersectionObserver let:intersecting top={400}>  {#if intersecting}     <section>       This message will Show if it is intersecting     </section>   {:else}     <section>       This message won't Show if it is intersecting     </section>  {/if} </IntersectionObserver>

That’s really it! You can see how the Intersection Observer component allows us to use <IntersectionObserver>  like a wrapper and define where the intersection should trigger, which is 400 pixels from the top in this example. As a reminder, this is all being exported as vanilla JavaScript! Super performant, no funny business. We’re sandwiching JavaScript and HTML together which is cool because we can see what the Intersection Observer is directly affecting, leaving no ambiguity and without being penalized for performance.

The OnMount function is necessary to tell Svelte that this code needs to run within the browser, as the Intersection Observer can’t be figured out ahead of time.

We’ll need to add some styling so that we can experience the observer in action, and we can do that directly in your App.svelte file. This might look super familiar if you have worked with any of the other front-end frameworks:

<style>   .somesection {     display: flex;     align-items: center;     justify-content: center;     width: 100%;     height: 100vh;   }      .somesection.even{     background: #ccc;   }            .content{     text-align: center;     width: 350px;   } </style>

Finally, we can copy and paste our Intersection Observer element four times to create more intersections. That gives us a mini web app that reactively adds and removes content as it comes into view — perfect to use with media, like lazy-loading. Check out a demo of the final result and be sure to crack open DevTools to see the Intersection Observer

Some final thoughts

My personal recommendation is to give Svelte a try. We’ve only scratched the surface of the framework in this article, but having converted my personal website to Svelte, I can confidently say that it is a pleasure to work with. It is performant, has a brilliant VSCode linter, and best of all, is easy to use. It may be small and new on the block, but I have a keen feeling that it is the relief from bloated “Goliath” frameworks, the “David” that frontend-ers have been looking for.

So should you use Svelte in a real project? Comparing risk and reward definitely comes into play. The community is smaller than other frameworks, meaning you’re likely to find less support and fewer tutorials to guide your along. At the same time, Svelte is in its third generation, meaning most of the gremlins should have been driven away, leaving a lean and reliable framework.

As with anything new, common sense rules, try it out with something non-commercial, take it for a spin, and see how you go.

Is there anything else? Funny you should ask! There are two co-projects that live in the Svelte Ecosystem: Sapper and Native. Sapper is a framework that utilizes Svelte for building full web applications, including routing, service workers, and all the good stuff you need to get started. I have used it to rebuild my personal website, and so far, I am a fan. Svelte Native is the most experimental of the Svelte projects, a NativeScript mobile app builder that utilizes Svelte under the hood. I confess that is where my knowledge on the subject ends. Luckily, it has a website with further information.

What do you think? Have you given Svelte a try? Do you think it stacks up to other frameworks? Let’s discuss it in the comments!

  1. Based on a mix of Github Contributions, NPM Downloads and StackOverflow topics
  2. State of JS review 2019

The post Getting Acquainted With Svelte, the New Framework on the Block appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

What I Like About Writing Styles with Svelte

There’s been a lot of well-deserved hype around Svelte recently, with the project accumulating over 24,000 GitHub stars. Arguably the simplest JavaScript framework out there, Svelte was written by Rich Harris, the developer behind Rollup. There’s a lot to like about Svelte (performance, built-in state management, writing proper markup rather than JSX), but the big draw for me has been its approach to CSS.

Single file components

​​

React does not have an opinion about how styles are defined
—React Documentation

​​​​

A UI framework that doesn’t have a built-in way to add styles to your components is unfinished.
—Rich Harris, creator of Svelte

In Svelte, you can write CSS in a stylesheet like you normally would on a typical project. You can also use CSS-in-JS solutions, like styled-components and Emotion, if you’d like. It’s become increasingly common to divide code into components, rather than by file type. React, for example, allows for the collocation of a components markup and JavaScript. In Svelte, this is taken one logical step further: the Javascript, markup and styling for a component can all exist together in a single `.svelte`​ file. If you’ve ever used single file components in Vue, then Svelte will look familiar.

// button.svelte <style>   button {     border-radius: 0;     background-color: aqua;   } </style>  <button>   <slot/> </button>

Styles are scoped by default

By default, styles defined within a Svelte file are scoped. Like CSS-in-JS libraries or CSS Modules, Svelte generates unique class names when it compiles to make sure the styles for one element never conflict with styles from another.

That means you can use simple element selectors like div and button in a Svelte component file without needing to work with class names. If we go back to the button styles in our earlier example, we know that a ruleset for <button> will only be applied to our <Button> component — not to any other HTML button elements within the page. If you were to have multiple buttons within a component and wanted to style them differently, you’d still need classes. Classes will also be scoped by Svelte.

The classes that Svelte generates look like gibberish because they are based on a hash of the component styles (e.g. svelte-433xyz). This is far easier than a naming convention like BEM. Admittedly though, the experience of looking at styles in DevTools is slightly worse as the class names lack meaning.

The markup of a Svelte component in DevTools.

It’s not an either/or situation. You can use Svelte’s scoped styling along with a regular stylesheet. I personally write component specific styles within .svelte files, but make use of utility classes defined in a stylesheet. For global styles to be available across an entire app — CSS custom properties, reusable CSS animations, utility classes, any ‘reset’ styles, or a CSS framework like Bootstrap — I suggest putting them in a stylesheet linked in the head of your HTML document.

It lets us create global styles

As we’ve just seen, you can use a regular stylesheet to define global styles. Should you need to define any global styles from within a Svelte component, you can do that too by using :global. This is essentially a way to opt out of scoping when and where you need to.

For example, a modal component may want to toggle a class to style the body element:

<style> :global(.noscroll) {   overflow: hidden; } </style>

Unused styles are flagged

Another benefit of Svelte is that it will alert you about any unused styles during compilation. In other words, it searches for places where styles are defined but never used in the markup.

Conditional classes are terse and effortless

If the JavaScript variable name and the class name is the same, the syntax is incredibly terse. In this example, I’m creating modifier props for a full-width button and a ghost button.

<script>   export let big = false;   export let ghost = false; </script>  <style>   .big {     font-size: 20px;     display: block;     width: 100%;   }      .ghost {     background-color: transparent;     border: solid currentColor 2px;   } </style>          <button class:big class:ghost>   <slot/> </button>

A class of ghost will be applied to the element when a ghost prop is used, and a class of big is applied when a big prop is used.

<script>   import Button from './Button.svelte'; </script>  <Button big ghost>Click Me</Button>

Svelte doesn’t require class names and prop names to be identical.

<script>   export let primary = false;   export let secondary = false; </script>  <button   class:c-btn--primary={primary}   class:c-btn--secondary={secondary}   class="c-btn">   <slot></slot> </button>

The above button component will always have a c-btn class but will include modifier classes only when the relevant prop is passed in, like this:

<Button primary>Click Me</Button>

That will generate this markup:

<button class="c-btn c-btn--primary">Click Me</button>

Any number of arbitrary classes can be passed to a component with a single prop:

<script> let class_name = ''; export { class_name as class }; </script>  <button class="c-btn {class_name}">   <slot /> </button>

Then, classes can be used much the same way you would with HTML markup:

<Button class="mt40">Click Me</Button>

From BEM to Svelte

Let’s see how much easier Svelte makes writing styles compared to a standard CSS naming convention. Here’s a simple component coded up using BEM.

.c-card {   border-radius: 3px;   border: solid 2px; }  .c-card__title {   text-transform: uppercase; }  .c-card__text {   color: gray; }  .c-card--featured {   border-color: gold; }

Using BEM, classes get long and ugly. In Svelte, things are a lot simpler.

<style> div {   border-radius: 3px;   border: solid 2px; }  h2 {   text-transform: uppercase; }  p {   color: gray; }  .featured {   border-color: gold; } </style>  <div class:featured>   <h2>What I Like About Writing Styles with Svelte</h2>   <p>     <slot />   </p> </div>

It plays well with preprocessors

CSS preprocessors feels a lot less necessary when working with Svelte, but they can work perfectly alongside one another by making use of a package called Svelte Preprocess. Support is available for Less, Stylus and PostCSS, but here we’ll look at Sass. The first thing we need to do is to install some dependencies:

npm install -D svelte-preprocess node-sass

Then we need to import autoPreprocess in rollup.config.js at the top of the file.

import autoPreprocess from 'svelte-preprocess';

Next, let’s find the plugins array and add preprocess: autoPreprocess() to Svelte:

export default {   plugins: [     svelte({       preprocess: autoPreprocess(),       ...other stuff

Then all we need to do is specify that we’re using Sass when we’re working in a component file, using type="text/scss" or lang="scss" to the style tag.

<style type="text/scss">   $ pink: rgb(200, 0, 220);   p {     color: black;     span {       color: $ pink;     }   } </style>

Dynamic values without a runtime

We’ve seen that Svelte comes with most of the benefits of CSS-in-JS out-of-the-box — but without external dependencies! However, there’s one thing that third-party libraries can do that Svelte simply can’t: use JavaScript variables in CSS.

The following code is not valid and will not work:

<script>   export let cols = 4; </script>  <style>   ul {     display: grid;     width: 100%;     grid-column-gap: 16px;     grid-row-gap: 16px;     grid-template-columns: repeat({cols}, 1fr);   } </style>  <ul>   <slot /> </ul>

We can, however, achieve similar functionality by using CSS variables.

<script>   export let cols = 4; </script>  <style>   ul {     display: grid;     width: 100%;     grid-column-gap: 16px;     grid-row-gap: 16px;     grid-template-columns: repeat(var(--columns), 1fr);   } </style>  <ul style="--columns:{cols}">   <slot /> </ul>

I’ve written CSS in all kinds of different ways over the years: Sass, Shadow DOM, CSS-in-JS, BEM, atomic CSS and PostCSS. Svelte offers the most intuitive, approachable and user-friendly styling API. If you want to read more about this topic then check out the aptly titled The Zen of Just Writing CSS by Rich Harris.

The post What I Like About Writing Styles with Svelte appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]