Tag: generator

Social Image Generator + Jetpack

I feel like my quest to make sure this site had pretty sweet (and automatically-generated) social media images (e.g. Open Graph) came to a close once I found Social Image Generator.

The trajectory there was that I ended up talking about it far too much on ShopTalk, to the point it became a common topic in our Discord (join via Patreon), Andy Bell pointed me at Daniel Post’s Social Image Generator and I immediately bought and installed it. I heard from Daniel over Twitter, and we ended up having long conversations about the plugin and my desires for it. Ultimately, Daniel helped me code up some custom designs and write logic to create different social media image designs depending on the information it had (for example, if we provide quote text, it uses a special design for that).

As you likely know, Automattic has been an awesome and long time sponsor for this site, and we often promote Jetpack as a part of that (as I’m a heavy user of it, it’s easy to talk about). One of Jetpack’s many features is helping out with social media. (I did a video on how we do it.) So, it occurred to me… maybe this would be a sweet feature for Jetpack. I mentioned it to the Automattic team and they were into the idea of talking to Daniel. I introduced them back in May, and now it’s September and… Jetpack Acquires WordPress Plugin Social Image Generator

“When I initially saw Social Image Generator, the functionality looked like a ideal fit with our existing social media tools,’ said James Grierson, General Manager of Jetpack. ‘I look forward to the future functionality and user experience improvements that will come out of this acquisition. The goal of our social product is to help content creators expand their audience through increased distribution and engagement. Social Image Generator will be a key component of helping us deliver this to our customers.”

Daniel will also be joining Jetpack to continue developing Social Image Generator and integrating it with Jetpack’s social media features.

Rob Pugh

Heck yeah, congrats Daniel. My dream for this thing is that, eventually, we could start building social media images via regular WordPress PHP templates. The trick is that you need something to screenshot them, like Puppeteer or Playwright. An average WordPress install doesn’t have that available, but because Jetpack is fundamentally a service that leverages the great WordPress cloud to do above-and-beyond things, this is in the realm of possibility.

WP Tavern also covered the news:

Automattic is always on the prowl for companies that are doing something interesting in the WordPress ecosystem. The Social Image Generator plugin expertly captured a new niche with an interface that feels like a natural part of WordPress and impressed our chief plugin critic, Justin Tadlock, in a recent review.

“Automattic approached me and let me know they were fans of my plugin,” Post said. “And then we started talking to see what it would be like to work together. We were actually introduced by Chris Coyier from CSS-Tricks, who uses both our products.”

Sarah Gooding

Just had to double-toot my own horn there, you understand.


The post Social Image Generator + Jetpack appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,

How I Made a Generator for SVG Loaders With Sass and SMIL Options

While learning Vue.js, I started building free web tools that involved the exploration of SVG, with the goal of learning something about both! Let’s take a look at one of those tools: a generator that makes SVG loaders and lets you choose between SMIL or Sass animation, different styles, colors, shapes, and effects. It even lets you paste in a custom path or text, and then download the final SVG, copy the code, or open a demo over at CodePen.

How it started

Three coincidences led me to build a generator for SVG loaders.

Coincidence 1: Sarah Drasner’s book

The first time I read about Sass loops was in Sarah Drasner’s SVG Animations. She shows how to stagger animations with a Sass function (like the does in Chapter 6, “Animating Data Visualizations”).

I was inspired by that chapter and the possibilities of Sass loops.

Coincidence 2: A GIF

At that same point in life, I was asked to replicate a “loader” element, similar to Apple’s old classic.

A round segmented spinner where each segment fades in and out in succession to create a circling effect.
This is a mockup of the loader I was asked to make.

I referenced Sarah’s example to make it happen. This is the Sass loop code I landed on:

@for $ i from 1 to 12 {   .loader:nth-of-type(#{$ i}) {     animation: 1s $ i * 0.08s opacityLoader infinite;   } } @keyframes opacityLoader {  to { opacity: 0; } }

This defines a variable for a number (i) from 1 to 12 that increases the delay of the animation with every :nth-child element. It was the perfect use case to animate as many elements as I wanted with only two lines of Sass, saving me CSS declarations for each of the delays I needed. This is the same animation, but written in vanilla CSS to show the difference:

.loader:nth-of-type(1) {   animation: 1s 0.08s opacityLoader infinite; } .loader:nth-of-type(2) {   animation: 1s 0.16s opacityLoader infinite; }  /* ... */  .loader:nth-of-type(12) {   animation: 1s 0.96s opacityLoader infinite; } @keyframes opacityLoader {   to { opacity: 0; } }

Coincidence 3: An idea

With these things going on in my head, I had an idea for a gallery of loaders, where each loader is made from the same Sass loop. I always struggle to find these kinds of things online, so I thought it might be useful for others, not to mention myself.

I had already built this kind of thing before as a personal project, so I ended up building a loader generator. Let me know if you find bugs in it!

One loader, two outputs

I was blocked by my own developer skills while creating a generator that produces the right Sass output. I decided to try another animation approach with SMIL animations, and that’s what I wound up deciding to use.

But then I received some help (thanks, ekrof!) and got Sass to work after all.

So, I ended up adding both options to the generator. I found it was a challenge to make both languages return the same result. In fact, they sometimes produce different results.

SMIL vs. CSS/Sass

I learned quite a bit about SMIL and CSS/Sass animations along the way. These are a few of the key takeaways that helped me on my way to making the generator:

  • SMIL doesn’t rely on any external resources. It animates SVG via presentation attributes directly in the SVG markup. That’s something that neither CSS nor Sass can do.
  • SMIL animations are preserved when an SVG is embedded as an image or as a background image. It is possible to add a CSS <style> block directly inside the SVG, but not so much with Sass, of course. That’s why there is an option to download the actual SVG file when selecting the SMIL option in the generator.
  • SMIL animations look a bit more fluid. I couldn’t find the reason for this (if anyone has any deeper information here, please share!). I though it was related to GPU acceleration, but it seems they both use the same animation engine.
Two spinners, one left and one right. They are both red and consist of circles that fade in and out in succession as an animated GIF.
SMIL (left) and Sass (right)

You might notice a difference in the chaining of the animations between both languages:

  • I used additive="sum" in SMIL to add animations one after the other. This makes sure each new animation effect avoids overriding the previous animation.
  • That said, in CSS/Sass, the W3C points out that [when] multiple animations are attempting to modify the same property, then the animation closest to the end of the list of names wins.

That’s why the order in which animations are applied might change the Sass output.

Working with transforms

Working with transformations in the loader’s styling was a big issue. I had applied transform: rotate inline to each shape because it’s a simple way to place them next to each other in a circle and with a face pointing toward the center.

<svg>   <!-- etc. -->   <use class="loader" xlink:href="#loader" transform="rotate(0 50 50)" />   <use class="loader" xlink:href="#loader" transform="rotate(30 50 50)" />   <use class="loader" xlink:href="#loader" transform="rotate(60 50 50)" />   <!-- etc. --> </svg>

I could declare a type in SMIL with <animateTransform> (e.g. scale or translate) to add that specific transform to the original transformation of each shape:

<animateTransform   attributeName="transform"   type="translate"   additive="sum"   dur="1s"   :begin="`$ {i * 0.08}s`"   repeatCount="indefinite"   from="0 0"   to="10" />

But instead, transform in CSS was overriding any previous transform applied to the inline SVG. In other words, the original position reset to 0 and showed a very different result from what SMIL produced. That meant the animations wound up looking identical no matter what.

The same two red spinners as before but with different results. The SMIL version on the left seems to work as expected but the Sass one on the right doesn't animate in a circle like it should.

The (not very pretty) solution to make the Sass similar to SMIL was to place each shape inside a group (<g>) element, and apply the inline rotation to the groups, and the animation to the shapes. This way, the inline transform isn’t affected by the animation.

<svg>   <!-- etc. -->   <g class="loader" transform="rotate(0 50 50)">     <use xlink:href="#loader" />   </g>   <g class="loader" transform="rotate(30 50 50)">     <use xlink:href="#loader" />   </g>   <!-- etc. --> </svg>

Now both languages have a very similar result.

The technology I used

I used Vue.js and Nuxt.js. Both have great documentation, but there are more specific reasons why I choose them.

I like Vue for lots of reasons:

  • Vue encapsulates HTML, CSS, and JavaScript as a “single file component” where all the code lives in a single file that’s easier to work with.
  • The way Vue binds and dynamically updates HTML or SVG attributes is very intuitive.
  • HTML and SVG don’t require any extra transformations (like making the code JSX-compatible).

As far as Nuxt goes:

  • It has a quick boilerplate that helps you focus on development instead of configuration.
  • There’s automatic routing and it supports auto-importing components.
  • It’s a good project structure with pages, components, and layouts.
  • It’s easier to optimize for SEO, thanks to meta tags.

Let’s look at a few example loaders

What I like about the end result is that the generator isn’t a one-trick pony. There’s no one way to use it. Because it outputs both SMIL and CSS/Sass, there are several ways to integrate a loader into your own project.

Download the SMIL SVG and use it as a background image in CSS

Like I mentioned earlier, SMIL features are preserved when an SVG is used as a background image file. So, simply download the SVG from the generator, upload it to your server, and reference it in CSS as a background image.

Similarly, we could use the SVG as a background image of a pseudo-element:

Drop the SVG right into the HTML markup

The SVG doesn’t have to be a background image. It’s just code, after all. That means we can simply drop the code from the generator into our own markup and let SMIL do its thing.

Use a Sass loop on the inline SVG

This is what I was originally inspired to do, but ran into some roadblocks. Instead of writing CSS declarations for each animation, we can use the Sass loop produced by the generator. The loop targets a .loader class that’s already applied to the outputted SVG. So, once Sass is compiled to CSS, we get a nice spinning animation.

I’m still working on this

My favorite part of the generator is the custom shape option where you can add text, emojis, or any SVG element to the mix:

The same circle spinner but using custom SVG shapes: one a word, one a poop emoji, and bright pink and orange asterisk.
Custom text, emoji, and SVG

What I would like to do is add a third option for styles to have just one element where you get to work with your own SVG element. That way, there’s less to work with, while allowing for simpler outputs.

The challenge with this project is working with custom values for so many things, like duration, direction, distance, and degrees. Another challenge for me personally is becoming more familiar with Vue because I want to go back and clean up that messy code. That said, the project is open source, and pull requests are welcome! Feel free to send suggestions, feedback, or even Vue course recommendations, especially ones related to SVG or making generators.

This all started with a Sass loop that I read in a book. It isn’t the cleanest code in the world, but I’m left blown away by the power of SMIL animations. I highly recommend Sarah Soueidan’s guide for a deeper dive into what SMIL is capable of doing.

If you’re curious about the safety of SMIL, that is for good reason. There was a time when Chrome was going to entirely deprecated SMIL (see the opening note in MDN). But that deprecation has been suspended and hasn’t (seemingly) been talked about in a while.

Can I use SMIL?

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Firefox IE Edge Safari
5 4 No 79 6

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
92 90 3 6.0-6.1

The post How I Made a Generator for SVG Loaders With Sass and SMIL Options appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Building a Cool Front End Thing Generator

Whether you are just starting out on the front end, or you’ve been doing it for a long time, building a tool that can generate some cool front-end magic can help you learn something new, develop your skills and maybe even get you a little notoriety.

You might have run across some of these popular online generators:

I’ve had fun building a few of these myself over the years. Basically, any time you run across some cool front-end thing, there might be an opportunity to make an interactive generator for that thing.

In this case, we are going to make an Animated Background Gradient Generator.

Scaffolding the project in Next

A nice thing about these projects is that they’re all yours. Choose whatever stack you want and get going. I’m a big fan of Next.js, so for this project, I’m going to start as a basic Create Next App project.

npx create-next-app animated-gradient-background-generator 

This generates all the files we need to get started. We can edit pages/index.js to be the shell for our project.

import Head from "next/head" import Image from "next/image" export default function Home() {   return (     <>       <Head>         <title>Animated CSS Gradient Background Generator</title>         <meta name="description" content="A tool for creating animated background gradients in pure CSS." />         <link rel="icon" href="/favicon.ico" />       </Head>       <main>         <h1>           Animated CSS Gradient Background Generator         </h1>       </main>     </>   ) }

Animated gradients?

At the time I’m writing this article, if you do a search for animated CSS gradient background, the first result is this Pen by Manuel Pinto.

Let’s take a look at the CSS:

body {   background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);   background-size: 400% 400%;   animation: gradient 15s ease infinite; }  @keyframes gradient {   0% {     background-position: 0% 50%;   }   50% {     background-position: 100% 50%;   }   100% {     background-position: 0% 50%;   } } 

This is a great example that we can use as the foundation for the generated animation.

A React component to describe an animated gradient

We can break out a few possible configurable options for the generator:

  • An array of gradient colors
  • The angle of the gradient
  • The speed of the animation

To put in context, we want to provide these settings as data throughout our little app using a higher-order component, context/SettingsContext.js, along with some defaults.

import React, { useState, createContext } from "react"  const SettingsContext = createContext({ colorSelection: [] })  const SettingsProvider = ({ children }) => {   const [colorSelection, setColorSelection] = useState([     "deepskyblue",     "darkviolet",     "blue",   ])   const [angle, setAngle] = useState(300)   const [speed, setSpeed] = useState(5)      return (     <SettingsContext.Provider       value={{         colorSelection,         setColorSelection,         angle,         setAngle,         speed,         setSpeed,       }}     >       {children}     </SettingsContext.Provider>   ) }  export { SettingsContext, SettingsProvider }

For our generator’s components, we want to create:

  • a control components to adjust these settings,
  • a visual display component for generated animated gradient, and
  • a component for the CSS code output.

Let’s start with a Controls component that contains the various inputs we used to adjust the settings.

import Colors from "./Colors"  const Controls = (props) => (   <>     <Colors />   </> )  export default Controls

We can add our SettingsProvider and Controls components to pages/index.js:

import Head from "next/head" import Image from "next/image" import { SettingsProvider } from "../context/SettingsContext" import Controls from "../components/Controls" import Output from "../components/Output"  export default function Home() {   return (     <>       <Head>         ...       </Head>        <SettingsProvider>         <main style={{ textAlign: "center", padding: "64px" }}>           <h1>Animated CSS Gradient Background Generator</h1>           <Controls />           <Output />         </main>       </SettingsProvider>     </>   ) }

Our SettingsProvider begins with the three colors from our CodePen example as defaults. We can verify that we are getting the color settings via our SettingsContext in a new Colors component.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext"  const Colors = () => {   const { colorSelection } = useContext(SettingsContext)   return (     <>       {colorSelection.map((color) => (         <div>{color}</div>       ))}     </>   ) }  export default Colors

Let’s use the Colors component to display individual color swatches with a small button to delete via our SettingsContext.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext"  const Colors = () => {   const { colorSelection, setColorSelection } = useContext(SettingsContext)    const onDelete = (deleteColor) => {     setColorSelection(colorSelection.filter((color) => color !== deleteColor))   }    return (     <div>       {colorSelection.map((color) => (         <div           key={color}           style={{             background: color,             display: "inline-block",             padding: "32px",             margin: "16px",             position: "relative",             borderRadius: "4px",           }}         >           <button             onClick={() => onDelete(color)}             style={{               background: "crimson",               color: "white",               display: "inline-block",               borderRadius: "50%",               position: "absolute",               top: "-8px",               right: "-8px",               border: "none",               fontSize: "18px",               lineHeight: 1,               width: "24px",               height: "24px",               cursor: "pointer",               boxShadow: "0 0 1px #000",             }}           >             ×           </button>         </div>       ))}     </div>   ) }  export default Colors

You may notice that we have been using inline styles for CSS at this point. Who cares! We’re having fun here, so we can do whatever floats our boats.

Handling colors

Next, we create an AddColor component with a button that opens a color picker used to add more colors to the gradient.

For the color picker, we will install react-color and use the ChromePicker option.

npm install react-color

Once again, we will utilize SettingsContext to update the gradient color selection.

import React, { useState, useContext } from "react" import { ChromePicker } from "react-color" import { SettingsContext } from "../context/SettingsContext"  const AddColor = () => {   const [color, setColor] = useState("white")   const { colorSelection, setColorSelection } = useContext(SettingsContext)    return (     <>       <div style={{ display: "inline-block", paddingBottom: "32px" }}>         <ChromePicker           header="Pick Colors"           color={color}           onChange={(newColor) => {             setColor(newColor.hex)           }}         />       </div>       <div>         <button           onClick={() => {             setColorSelection([...colorSelection, color])           }}           style={{             background: "royalblue",             color: "white",             padding: "12px 16px",             borderRadius: "8px",             border: "none",             fontSize: "16px",             cursor: "pointer",             lineHeight: 1,           }}         >           + Add Color         </button>       </div>     </>   ) }  export default AddColor

Handling angle and speed

Now that our color controls are finished, let’s add some components with range inputs for setting the angle and animation speed.

Here’s the code for AngleRange, with SpeedRange being very similar.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext"  const AngleRange = () => {   const { angle, setAngle } = useContext(SettingsContext)    return (     <div style={{ padding: "32px 0", fontSize: "18px" }}>       <label         style={{           display: "inline-block",           fontWeight: "bold",           width: "100px",           textAlign: "right",         }}         htmlFor="angle"       >         Angle       </label>       <input         type="range"         id="angle"         name="angle"         min="-180"         max="180"         value={angle}         onChange={(e) => {           setAngle(e.target.value)         }}         style={{           margin: "0 16px",           width: "180px",           position: "relative",           top: "2px",         }}       />       <span         style={{           fontSize: "14px",           padding: "0 8px",           position: "relative",           top: "-2px",           width: "120px",           display: "inline-block",         }}       >         {angle} degrees       </span>     </div>   ) }  export default AngleRange

Now for the fun part: rendering the animated background. Let’s apply this to the entire background of the page with an AnimatedBackground wrapper component.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext" const AnimatedBackground = ({ children }) => {   const { colorSelection, speed, angle } = useContext(SettingsContext) const background =   "linear-gradient(" + angle + "deg, " + colorSelection.toString() + ")" const backgroundSize =   colorSelection.length * 60 + "%" + " " + colorSelection.length * 60 + "%" const animation =   "gradient-animation " +   colorSelection.length * Math.abs(speed - 11) +   "s ease infinite" return (   <div style={{ background, "background-size": backgroundSize, animation, color: "white" }}>     {children}   </div>   ) } export default AnimatedBackground

We’re calling the CSS animation for the gradient gradient-animation. We need to add that to styles/globals.css to trigger the animation:

@keyframes gradient-animation {   0% {     background-position: 0% 50%;   }   50% {     background-position: 100% 50%;   }   100% {     background-position: 0% 50%;   } } 

Making it useful to users

Next, let’s add some code output so people can copy and paste the generated CSS and use in their own projects.

import React, { useContext, useState } from "react" import { SettingsContext } from "../context/SettingsContext" const Output = () => {   const [copied, setCopied] = useState(false) const { colorSelection, speed, angle } = useContext(SettingsContext) const background =   "linear-gradient(" + angle + "deg," + colorSelection.toString() + ")" const backgroundSize =   colorSelection.length * 60 + "%" + " " + colorSelection.length * 60 + "%" const animation =   "gradient-animation " +   colorSelection.length * Math.abs(speed - 11) +   "s ease infinite" const code = `.gradient-background {   background: $  {background};   background-size: $  {backgroundSize};   animation: $  {animation}; } @keyframes gradient-animation {   0% {     background-position: 0% 50%;   }   50% {     background-position: 100% 50%;   }   100% {     background-position: 0% 50%;   } }` return (     <div       style={{ position: "relative", maxWidth: "640px", margin: "64px auto" }}     >       <pre         style={{           background: "#fff",           color: "#222",           padding: "32px",           width: "100%",           borderRadius: "4px",           textAlign: "left",           whiteSpace: "pre",           boxShadow: "0 2px 8px rgba(0,0,0,.33)",           overflowX: "scroll",         }}       >         <code>{code}</code>         <button           style={{             position: "absolute",             top: "8px",             right: "8px",             background: "royalblue",             color: "white",             padding: "8px 12px",             borderRadius: "8px",             border: "none",             fontSize: "16px",             cursor: "pointer",             lineHeight: 1,           }}           onClick={() => {             setCopied(true)             navigator.clipboard.writeText(code)           }}         >           {copied ? "copied" : "copy"}         </button>       </pre>     </div>   ) } export default Output

Making it fun

It is sometimes fun (and useful) to add a button that sets random values on a generator like this. That gives people a way to quickly experiment and see what kinds of results they can get out of the tool. It is also an opportunity to look up cool stuff like how to generate random hex colors.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext"  const Random = () => {   const { setColorSelection, setAngle, setSpeed } = useContext(SettingsContext)    const goRandom = () => {     const numColors = 3 + Math.round(Math.random() * 3)     const colors = [...Array(numColors)].map(() => {       return "#" + Math.floor(Math.random() * 16777215).toString(16)     })     setColorSelection(colors)     setAngle(Math.floor(Math.random() * 361))     setSpeed(Math.floor(Math.random() * 10) + 1)   }    return (     <div style={{ padding: "48px 0 16px" }}>       <button         onClick={goRandom}         style={{           fontSize: "24px",           fontWeight: 200,           background: "rgba(255,255,255,.9)",           color: "blue",           padding: "24px 48px",           borderRadius: "8px",           cursor: "pointer",           boxShadow: "0 0 4px #000",           border: "none",         }}       >         RANDOM       </button>     </div>   ) }  export default Random

Wrapping up

There will be a few final things you’ll want to do to wrap up your project for initial release:

  • Update package.json with your project information.
  • Add some links to your personal site, the project’s repository and give credit where its due.
  • Update the README.md file that was generated with default content by Create Next App.

That’s it! We’re ready to release our new cool front end thing generator and reap the rewards of fame and fortune that await us!

You can see the code for this project on GitHub and the demo is hosted on Netlify.


The post Building a Cool Front End Thing Generator appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Hack the “Deploy to Netlify” Button Using Environment Variables to Make a Customizable Site Generator

If you’re anything like me, you like being lazy shortcuts. The “Deploy to Netlify” button allows me to take this lovely feature of my personality and be productive with it.

Deploy to Netlify

Clicking the button above lets me (or you!) instantly clone my Next.js starter project and automatically deploy it to Netlify. Wow! So easy! I’m so happy!

Now, as I was perusing the docs for the button the other night, as one does, I noticed that you can pre-fill environment variables to the sites you deploy with the button. Which got me thinking… what kind of sites could I customize with that?

Ah, the famed “link in bio” you see all over social media when folks want you to see all of their relevant links in life. You can sign up for the various services that’ll make one of these sites for you, but what if you could make one yourself without having to sign up for yet another service?

But, we also are lazy and like shortcuts. Sounds like we can solve all of these problems with the “Deploy to Netlify” (DTN) button, and environment variables.

How would we build something like this?

In order to make our DTN button work, we need to make two projects that work together:

  • A template project (This is the repo that will be cloned and customized based on the environment variables passed in.)
  • A generator project (This is the project that will create the environment variables that should be passed to the button.)

I decided to be a little spicy with my examples, and so I made both projects with Vite, but the template project uses React and the generator project uses Vue.

I’ll do a high-level overview of how I built these two projects, and if you’d like to just see all the code, you can skip to the end of this post to see the final repositories!

The Template project

To start my template project, I’ll pull in Vite and React.

npm init @vitejs/app

After running this command, you can follow the prompts with whatever frameworks you’d like!

Now after doing the whole npm install thing, you’ll want to add a .local.env file and add in the environment variables you want to include. I want to have a name for the person who owns the site, their profile picture, and then all of their relevant links.

VITE_NAME=Cassidy Williams VITE_PROFILE_PIC=https://github.com/cassidoo.png VITE_GITHUB_LINK=https://github.com/cassidoo VITE_TWITTER_LINK=https://twitter.com/cassidoo

You can set this up however you’d like, because this is just test data we’ll build off of! As you build out your own application, you can pull in your environment variables at any time for parsing with import.meta.env. Vite lets you access those variables from the client code with VITE_, so as you play around with variables, make sure you prepend that to your variables.

Ultimately, I made a rather large parsing function that I passed to my components to render into the template:

function getPageContent() {   // Pull in all variables that start with VITE_ and turn it into an array   let envVars = Object.entries(import.meta.env).filter((key) => key[0].startsWith('VITE_'))    // Get the name and profile picture, since those are structured differently from the links   const name = envVars.find((val) => val[0] === 'VITE_NAME')[1].replace(/_/g, ' ')   const profilePic = envVars.find((val) => val[0] === 'VITE_PROFILE_PIC')[1]      // ...      // Pull all of the links, and properly format the names to be all lowercase and normalized   let links = envVars.map((k) => {     return [deEnvify(k[0]), k[1]]   })    // This object is what is ultimately sent to React to be rendered   return { name, profilePic, links } }  function deEnvify(str) {   return str.replace('VITE_', '').replace('_LINK', '').toLowerCase().split('_').join(' ') }

I can now pull in these variables into a React function that renders the components I need:

// ...   return (     <div>       <img alt={vars.name} src={vars.profilePic} />       <p>{vars.name}</p>       {vars.links.map((l, index) => {         return <Link key={`link$ {index}`} name={l[0]} href={l[1]} />       })}     </div>   )  // ...

And voilà! With a little CSS, we have a “link in bio” site!

Now let’s turn this into something that doesn’t rely on hard-coded variables. Generator time!

The Generator project

I’m going to start a new Vite site, just like I did before, but I’ll be using Vue for this one, for funzies.

Now in this project, I need to generate the environment variables we talked about above. So we’ll need an input for the name, an input for the profile picture, and then a set of inputs for each link that a person might want to make.

In my App.vue template, I’ll have these separated out like so:

<template>   <div>     <p>       <span>Your name:</span>       <input type="text" v-model="name" /> 	</p>     <p>       <span>Your profile picture:</span>	       <input type="text" v-model="propic" />     </p>   </div>    <List v-model:list="list" />    <GenerateButton :name="name" :propic="propic" :list="list" /> </template>

In that List component, we’ll have dual inputs that gather all of the links our users might want to add:

<template>   <div class="list">     Add a link: <br />     <input type="text" v-model="newItem.name" />     <input type="text" v-model="newItem.url" @keyup.enter="addItem" />     <button @click="addItem">+</button>      <ListItem       v-for="(item, index) in list"       :key="index"       :item="item"       @delete="removeItem(index)"     />   </div> </template>

So in this component, there’s the two inputs that are adding to an object called newItem, and then the ListItem component lists out all of the links that have been created already, and each one can delete itself.

Now, we can take all of these values we’ve gotten from our users, and populate the GenerateButton component with them to make our DTN button work!

The template in GenerateButton is just an <a> tag with the link. The power in this one comes from the methods in the <script>.

// ... methods: {   convertLink(str) {     // Convert each string passed in to use the VITE_WHATEVER_LINK syntax that our template expects     return `VITE_$ {str.replace(/ /g, '_').toUpperCase()}_LINK`   },   convertListOfLinks() {     let linkString = ''          // Pass each link given by the user to our helper function     this.list.forEach((l) => {       linkString += `$ {this.convertLink(l.name)}=$ {l.url}&`     })      return linkString   },   // This function pushes all of our strings together into one giant link that will be put into our button that will deploy everything!   siteLink() {     return (       // This is the base URL we need of our template repo, and the Netlify deploy trigger       'https://app.netlify.com/start/deploy?repository=https://github.com/cassidoo/link-in-bio-template#' +       'VITE_NAME=' +       // Replacing spaces with underscores in the name so that the URL doesn't turn that into %20       this.name.replace(/ /g, '_') +       '&' +       'VITE_PROFILE_PIC=' +       this.propic +       '&' +       // Pulls all the links from our helper function above       this.convertListOfLinks()     )   }, }, 

Believe it or not, that’s it. You can add whatever styles you like or change up what variables are passed (like themes, toggles, etc.) to make this truly customizable!

Put it all together

Once these projects are deployed, they can work together in beautiful harmony!

This is the kind of project that can really illustrate the power of customization when you have access to user-generated environment variables. It may be a small one, but when you think about generating, say, resume websites, e-commerce themes, “/uses” websites, marketing sites… the possibilities are endless for turning this into a really cool boilerplate method.


The post Hack the “Deploy to Netlify” Button Using Environment Variables to Make a Customizable Site Generator appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , , , , ,
[Top]

Comparing Static Site Generator Build Times

There are so many static site generators (SSGs). It’s overwhelming trying to decide where to start. While an abundance of helpful articles may help wade through the (popular) options, they don’t magically make the decision easy.

I’ve been on a quest to help make that decision easier. A colleague of mine built a static site generator evaluation cheatsheet. It provides a really nice snapshot across numerous popular SSG choices. What’s missing is how they actually perform in action.

One feature every static site generator has in common is that it takes input data, runs it through a templating engine, and outputs HTML files. We typically refer to this process as The Build.

There’s too much nuance, context, and variability needed to compare how various SSGs perform during the build process to display on a spreadsheet — and thus begins our test to benchmark build times against popular static site generators.

This isn’t just to determine which SSG is fastest. Hugo already has that reputation. I mean, they say it on their website — The world’s fastest framework for building websites — so it must be true!

This is an in-depth comparison of build times across multiple popular SSGs and, more importantly, to analyze why those build times look the way they do. Blindly choosing the fastest or discrediting the slowest would be a mistake. Let’s find out why.

The tests

The testing process is designed to start simple — with just a few popular SSGs and a simple data format. A foundation on which to expand to more SSGs and more nuanced data. For today, the test includes six popular SSG choices:

Each test used the following approach and conditions:

  • The data source for each build are Markdown files with a randomly-generated title (as frontmatter) and body (containing three paragraphs of content).
  • The content contains no images.
  • Tests are run in series on a single machine, making the actual values less relevant than the relative comparison among the lot.
  • The output is plain text on an HTML page, run through the default starter, following each SSG’s respective guide on getting started.
  • Each test is a cold run. Caches are cleared and Markdown files are regenerated for every test.

These tests are considered benchmark tests. They are using basic Markdown files and outputting unstyled HTML into the built output.

In other words, the output is technically a website that could be deployed to production, though it’s not really a real-world scenario. Instead, this provides a baseline comparison among these frameworks. The choices you make as a developer using one of these frameworks will adjust the build times in various ways (usually by slowing it down).

For example, one way in which this doesn’t represent the real-world is that we’re testing cold builds. In the real-world, if you have 10,000 Markdown files as your data source and are using Gatsby, you’re going to make use of Gatsby’s cache, which will greatly reduce the build times (by as much as half).

The same can be said for incremental builds, which are related to warm versus cold runs in that they only build the file that changed. We’re not testing the incremental approach in these tests (at this time).

The two tiers of static site generators

Before we do that, let’s first consider that there are really two tiers of static site generators. Let’s call them basic and advanced.

  • Basic generators (which are not basic under the hood) are essentially a command-line interface (CLI) that takes in data and outputs HTML, and can often be extended to process assets (which we’re not doing here).
  • Advanced generators offer something in addition to outputting a static site, such as server-side rendering, serverless functions, and framework integration. They tend to be configured to be more dynamic right out of the box.

I intentionally chose three of each type of generator in this test. Falling into the basic bucket would be Eleventy, Hugo, and Jekyll. The other three are based on a front-end framework and ship with various amounts of tooling. Gatsby and Next are built on React, while Nuxt is built atop Vue.

Basic generators Advanced generators
Eleventy Gatsby
Hugo Next
Jekyll Nuxt

My hypothesis

Let’s apply the scientific method to this approach because science is fun (and useful)!

My hypothesis is that if an SSG is advanced, then it will perform slower than a basic SSG. I believe the results will reflect that because advanced SSGs have more overhead than basic SSGs. Thus, it’s likely that we’re going to see both groups of generators — basic and advanced — bundled together, in the results with basic generators moving significantly quicker.

Let me expand on that hypothesis a bit.

Linear(ish) and fast

Hugo and Eleventy will fly with smaller datasets. They are (relatively) simple processes in Go and Node.js, respectively, and their build output will reflect that. While both SSG will slow down as the number of files grows, I expect them to remain at the top of the class, though Eleventy may be a little less linear at scale, simply because Go tends to be more performant than Node.

Slow, then fast, but still slow

The advanced, or framework-bound SSGs, will start out and appear slow. I suspect a single-file test to contain a significant difference — milliseconds for the basic ones, compared to several seconds for Gatsby, Next, and Nuxt.

The framework-based SSGs are each built using webpack, bringing a significant amount of overhead along with it, regardless of the amount of content they are processing. That’s the baggage we sign up for in using those tools (more on this later).

But, as we add thousands of files, I suspect we’ll see the gap between the buckets close, though the advanced SSG group will stay farther behind by some significant amount.

In the advanced SSG group, I expect Gatsby to be the fastest, only because it doesn’t have a server-side component to worry about — but that’s just a gut feeling. Next and Nuxt may have optimized this to the point where, if we’re not using that feature, it won’t affect build times. And I suspect Nuxt will beat out Next, only because there is a little less overhead with Vue, compared to React.

Jekyll: The odd child

Ruby is infamously slow. It’s gotten more performant over time, but I don’t expect it to scale with Node, and certainly not with Go. And yet, at the same time, it doesn’t have the baggage of a framework.

At first, I think we’ll see Jekyll as pretty speedy, perhaps even indistinguishable from Eleventy. But as we get to the thousands of files, the performance will take a hit. My gut feeling is that there may exist a point at which Jekyll becomes the slowest of all six. We’ll push up to the 100,000 mark to see for sure.

A hand-drawn line chart showing build time on the y-axis and number of files on the x-asix, where Next is a green line, then nuxt is a yellow line, gatsby is a pink line jekyll is a blue line, eleventy is a teal line and hugo is an orange line. All lines show the build time increasing as the number of files increase, where jekyll has the sharpest slope.

The results are in!

The code that powers these tests are on GitHub. There’s also a site that shows the relative results.

After many iterations of building out a foundation on which these tests could be run, I ended up with a series of 10 runs in three different datasets:

  • Base: A single file, to compare the base build times
  • Small sites: From 1 to 1024 files, doubling each to time (to make it easier to determine if the SSGs scaled linearly)
  • Large sites: From 1,000 to 64,000 files, double on each run. I originally wanted to go up to 128,000 files, but hit some bottlenecks with a few of the frameworks. 64,000 ended up being enough to produce an idea of how the players would scale with even larger sites.

Click or tap the images to view them larger.

Summarizing the results

A few results were surprising to me, while others were expected. Here are the high-level points:

  • As expected, Hugo was the fastest, regardless of size. What I didn’t expect is that it wasn’t even close to any other generator, even at base builds (nor was it linear, but more on that below.)
  • The basic and advanced groups of SSGs are quite obvious when looking at the results for small sites. That was expected, but it was surprising to see Next is faster than Jekyll at 32,000 files, and faster than both Eleventy and Jekyll at 64,000 files. Also surprising is that Jekyll performed faster than Eleventy at 64,000 files.
  • None of the SSGs scale linearly. Next was the closest. Hugo has the appearance of being linear, but only because it’s so much faster than the rest.
  • I figured Gatsby to be the fastest among the advanced frameworks, and suspected it would be the one to get closer to the basics. But Gatsby turned out to be the slowest, producing the most dramatic curve.
  • While it wasn’t specifically mentioned in the hypothesis, the scale of differences was larger than I would have imagined. At one file, Hugo was approximately 170 times faster than Gatsby. But at 64,000 files, it was closer — about 25 times faster. That means that, while Hugo remains the fastest, it actually has the most dramatic exponential growth shape among the lot. It just looks linear because of the scale of the chart.

What does it all mean?

When I shared my results with the creators and maintainers of these SSGs, I generally received the same message. To paraphrase:

The generators that take more time to build do so because they are doing more. They are bringing more to the table for developers to work with, whereas the faster sites (i.e. the “basic” tools) focus their efforts largely in converting templates into HTML files.

I agree.

To sum it up: Scaling Jamstack sites is hard.

The challenges that will present themselves to you, Developer, as you scale a site will vary depending on the site you’re trying to build. That data isn’t captured here because it can’t be — every project is unique in some way.

What it really comes down to is your level of tolerance for waiting in exchange for developer experience.

For example, if you’re going to build a large, image-heavy site with Gatsby, you’re going to pay for it with build times, but you’re also given an immense network of plugins and a foundation on which to build a solid, organized, component-based website. Do the same with Jekyll, and it’s going to take a lot more effort to stay organized and efficient throughout the process, though your builds may run faster.

At work, I typically build sites with Gatsby (or Next, depending on the level of dynamic interactivity required). We’ve worked with the Gatsby framework to build a core on which we can rapidly build highly-customized, image-rich websites, packed with an abundance of components. Our builds become slower as the sites scale, but that’s when we get creative by implementing micro front-ends, offloading image processing, implementing content previews, along with many other optimizations.

On the side, I tend to prefer working with Eleventy. It’s usually just me writing code, and my needs are much simpler. (I like to think of myself as a good client for myself.) I feel I have more control over the output files, which makes it easier for me to get 💯s on client-side performance, and that’s important to me.

In the end, this isn’t only about what is fast or slow. It’s about what works best for you and how long you’re willing to wait.

Wrapping up

This is just the beginning! The goal of this effort was to create a foundation on which we can, together, benchmark relative build times across popular static site generators.

What ideas do you have? What holes can you poke in the process? What can we do to tighten up these tests? How can we make them more like real-world scenarios? Should we offload the processing to a dedicated machine?

These are the questions I’d love for you to help me answer. Let’s talk about it.


The post Comparing Static Site Generator Build Times appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , ,
[Top]

Layoutit Grid: Learning CSS Grid Visually With a Generator

Layoutit Grid is an interactive open source CSS Grid generator. It lets you draw your designs and see the code as you go. You can interact with the code, add or remove track lines and drag them around to change the sizing — and you get to see the CSS and HTML change in real time!

Animated gif of the tool which is split into three columns: one that sets the number of grid rows and columns, one to name and visualize the layout, and the last to see the code.
Add some tracks and see how they’re made in CSS

When you are done with a layout, you can create a CodePen or grab the code to jumpstart your next project. The tool brings the code to the forefront, helping you learn CSS grid as you work directly with it.

CSS Grid is a whole new way of thinking about layouts

We can now create robust responsive layouts for our web experiences. We can finally learn to design with a coherent set of layout tools instead of memorizing piles of hacks to force elements into position.

Now, I’m not saying a generator like this excuses us from knowing the code we write. We should all learn how CSS Grid and Flexbox work. Even if your stronghold is JavaScript, having a solid foundation in CSS knowledge is a powerful ally when communicating your ideas. When sharing a prototype for a component, a UX interaction, or even an algorithm in an online sandbox, the way in which your work is presented can make a big the difference. The ability to develop proper layouts — and define the styles that create them — is fundamental.

Crafting layouts in CSS should not be a daunting task. CSS Grid is actually quite fun to use! For example, using named grid areas feels like an ASCII art version of drawing a design on a piece of paper. Lets create the layout of a photos app, a feed of pics and the people in them side by side for its main content and the typical header, footer and a config sidebar.

.photos-app {   /* For our app layout, lets place things in a grid */   display: grid;   /* We want 3 columns and 3 rows, and these are the responsive      track sizes using `fr` (fraction of the remaining space) */   grid-template-columns: 20% 1fr 1fr;   grid-template-rows: 0.5fr 1.7fr 0.3fr;   /* Let's separate our tracks a bit */   gap: 1em;   /* We now have 3x3 cells, here is where each section is placed */   grid-template-areas:     "header header header"  /* a header stretching in the top row */     "config photos people"  /* a left sidebar, and our app content */     "footer footer footer"; /* and a footer along the bottom row  */ } 
 .the-header {   /* In each section, let's define the name we use to refence the area */   grid-area: "header"; }

This is just a small subset of what you can build with CSS Grid. The spec is quite flexible. Areas can also be placed directly using line numbers or names, or they can be placed implicitly by the browser, with the content distributed inside the grid automatically. And the spec continues to grow with additions, like subgrid.

At the same time, working with grids can be difficult, just like anything that requires a new way of thinking. It takes a lot of time to wrap our heads around this sort of thing. And one way to help do that is to…

Learn while playing

When you are learning CSS Grid, it is easy to feel intimidated by its notation and semantics. Until you develop some muscle memory for it, kickstarting the learning process with visual and interactive tools can be an excellent way to overcome that early trepidation. A lot of us have used generators while learning how to create shadows, gradients, Markdown tables, and so on. Generators, if built with care, are great learning aids.

Let’s use Layoutit Grid to recreate the same design in our example.

Generators like this aren’t meant to be leaned on forever; they’re a stepping stone. This particular one helps you experience the power of CSS Grid by materializing your designs in a few clicks along with the code to make it happen. This gives you the early wins that you need to push forward with the learning process. For some of us, generators permanently remain in our toolboxes. Not because we do not know how to craft the layouts by hand, but because having the visual feedback loop help us to quickly convert our ideas into code. So we keep playing with them.

Sarah Drasner has also created a CSS Grid generator that’s worth checking out as well.

Learn by building

Leniolabs recently open-sourced Layoutit Grid and added new features, like interactive code views, area edition, history and offline support. And there are several more in the making.

If you have ideas to improve the tool, get in touch! Open an issue and let’s discuss it on GitHub. Going into meta territory, you can also learn about the CSS Grid spec by helping us build the tool. 

We use the app to keep track of best practices in creating performant interactive web experiences. It is now powered by the newly released Vue 3 using <script setup> components and built with Vite, a new dev tool that doesn’t bundle the app while developing, which gives us instant feedback during development. If you are curious and want to build with us, fork the repo and let’s learn together!


The post Layoutit Grid: Learning CSS Grid Visually With a Generator appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,
[Top]

The Hero Generator

,
[Top]

It’s pretty cool how Netlify CMS works with any flat file site generator

Little confession here: when I first saw Netlify CMS at a glance, I thought: cool, maybe I’ll try that someday when I’m exploring CMSs for a new project. Then as I looked at it with fresh eyes: I can already use this! It’s a true CMS in that it adds a content management UI on top of any static site generator that works from flat files! Think of how you might build a site from markdown files with Gatsby, Jekyll, Hugo, Middleman, etc. You can create and edit Markdown files and the site’s build process runs and the site is created.

Netlify CMS gives you (or anyone you set it up for) a way to create/edit those Markdown files without having to use a code editor or know about Pull Requests on GitHub or anything. It’s a little in-browser app that gives you a UI and does the file manipulation and Git stuff behind the scenes.

Here’s an example.

Our conferences website is a perfect site to build with a static site generator.

It’s on GitHub, so it’s open to Pull Requests, and each conference is a Markdown file.

That’s pretty cool already. The community has contributed 77 Pull Requests already really fleshing out the content of the site, and the design, accessibility, and features as well!

I used 11ty to build the site, which works great with building out those Markdown files into a site, using Nunjucks templates. Very satisfying combo, I found, after a slight mostly configuration-related learning curve.

Enter Netlify CMS.

But as comfortable as you or I might be with a quick code edit and Pull Request, not everybody is. And even I have to agree that going to a URL quick, editing some copy in input fields, and clicking a save button is the easiest possible way to manage content.

That CMS UI is exactly what Netlify CMS gives you. Wanna see the entire commit for adding Netlify CMS?

It’s two files! That still kinda blows my mind. It’s a little SPA React app that’s entirely configurable with one file.

Cutting to the chase here, once it is installed, I now have a totally customized UI for editing the conferences on the site available right on the production site.

Netlify CMS doesn’t do anything forceful or weird, like attempt to edit the HTML on the production site directly. It works right into the workflow in the same exact way that you would if you were editing files in a code editor and committing in Git.

Auth & Git

You use Netlify CMS on your production site, which means you need authentication so that just you (and the people you want) have access to it. Netlify Identity makes that a snap. You just flip it on from your Netlify settings and it works.

I activated GitHub Auth so I could make logging in one-click for me.

The Git magic happens through a technology called Git Gateway. You don’t have to understand it (I don’t really), you just enable it in Netlify as part of Netlify Identity, and it forms the connection between your site and the Git repository.

Now when you create/edit content, actual Markdown files are created and edited (and whatever else is involved, like images!) and the change happens right in the Git repository.


I made this the footer of the site cause heck yeah.

The post It’s pretty cool how Netlify CMS works with any flat file site generator appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]