About

Friday, August 5, 2022

How I Added Scroll Snapping To My Twitter Timeline

CSS Scroll Snap allows websites to snap the web page or any other scroll container to a specific scroll position when the user performs a scrolling operation. This feature has been supported in all modern browsers for over two years, but many websites that could benefit from it are still not using it.

Scroll snapping is probably most associated with horizontal carousels (see Chris’s CSS-only approach) and particular web pages divided into full-screen slides. But why stop there? I believe that snapping can improve the scrolling experience on any web page that lays out items in a grid or feed.

For example, most shopping websites show products in a grid. Ideally, the user would like to jump between the grid rows with minimal effort. The user can press Space to scroll the page by roughly one screen (viewport height), but depending on the height of the grid rows, the scroll position will eventually get “out of sync” with the grid, and the user will have to re-adjust it manually.

If we add scroll snapping to this page, the user can consistently scroll to the next row with the Space key (pressing Shift + Space will scroll to the previous row). It’s pretty effortless.

I think that scroll snapping would be a welcome addition to this website. And it’s not even that complicated to implement. The CSS code that I used for this example is relatively simple:

html {
  scroll-snap-type: y proximity;
}

.product-item {
  scroll-snap-align: start;
  scroll-margin-top: 75px; /* height of web page’s sticky header */
}

You don’t have to wait if a website you visit regularly hasn’t yet added scroll snapping and you think it would improve your scrolling experience. You can add scroll snapping yourself — with user styles.

Adding user styles to websites

In the video above, you can see that I selected a user.css file in Safari’s advanced preferences. This file is a user style sheet. It contains CSS styles that I’ve written, stored in a local .css file, and added to Safari. These “user styles” are then applied to every web page I open in Safari.

Chrome and Firefox do not allow users to select a user style sheet. Firefox supported a similar feature called userContent.css in the past, but that feature was deprecated and disabled by default in 2019. I recommend the Stylus browser extension for these two browsers (and other Chromium-based browsers).

One significant advantage of Stylus is that it allows you to write user styles for specific websites and URLs. Safari’s user style sheet applies to all websites, but this can be worked around, e.g., by using the new :has() pseudo-class to create selectors that only match specific websites.

The Stylus extension has been reviewed by both Chrome and Firefox teams and received a badge that denotes high standards.

The CSS Cascading module defines a User Origin for styles the user adds. Safari’s user style sheet belongs to this origin, but the Stylus extension injects user styles to the Author Origin, where the website’s style sheets live. Specifically, Stylus inserts user styles directly to the page via a <style> element at the end of <html> which makes it the final style sheet on the page. Technically, this means styles added via Stylus are classified as author styles since they’re not in the User Origin, but I will continue to call them user styles because the user adds them.

However, it’s worth keeping this distinction in mind because it affects the cascade. When selector specificity is equal, a real user style is weaker than the page’s own style. This makes user styles an excellent fit for user defaults. Under the same conditions, a style added via Stylus is stronger than the page‘s style, so Stylus cannot as easily be used to define user defaults.

If we add !important to the mix, both real user styles and styles added via Stylus are stronger than the page’s styles. So when you want to impose your user styles on a website, it doesn’t matter if you use Safari’s “Style sheet” option or the Stylus extension. Your !important styles will win either way.

In the next section, I will use a set of !important user styles to enforce scroll snapping on the timeline page of Twitter’s website. My goal is to speed up the process of reading my Twitter timeline by avoiding awkward scroll positions where the topmost tweet is only partially on screen.

Scroll snap for Twitter’s timeline

After some experimentation, I’ve settled on the following CSS code. These styles work well in Firefox, but I’ve experienced some issues in Chrome and Safari. I will describe these issues in more detail later in the article, but for now, let’s focus on the behavior in Firefox.

html {
  scroll-snap-type: y mandatory !important;
}

/* tweets in the timeline are <article> elements */
article {
  scroll-snap-align: start !important;
}

/* un-stick the sticky header and make it “snappable” as well */
[aria-label="Home timeline"] > :first-child {
  position: static !important;
  scroll-snap-align: start !important;
}

/* hide the “new Tweets available” floating toast notification */
[aria-label="New Tweets are available."] {
  display: none !important;
}

It is necessary to add !important to each declaration because all the user styles must win over the web page’s own styles for our custom scroll snapping implementation to work correctly. I wish that instead of repeatedly writing !important, I could just put my user styles in an “important layer,” but such a CSS feature does not exist (yet).

Watch the video below to see my scroll snap user styles in action. Notice how each press on the Space key scrolls the next set of tweets into view, and the first tweet of each set is aligned to the top edge of the viewport. This allows me to read my timeline more quickly. When I need to go back to the previous set of tweets, I can press Shift + Space.

What I like about this type of scroll snapping is that it allows me to predict how far the page will scroll whenever I press Space. Each scroll distance equals the combined heights of the visible tweets that are entirely on the screen. In other words, the partially visible tweet at the bottom of the screen will move to the top of the screen, which is precisely what I want.

I know in advance that pressing Space will scroll Dave’s tweet to the top of the screen.

To try out my scroll snap user styles on your own Twitter timeline, follow these steps:

  1. Install the Stylus extension with Firefox Add-ons or the Chrome Web Store.
  2. Navigate to your Twitter timeline at https://twitter.com/home.
  3. Click the Stylus icon in the browser’s toolbar and click “this URL” in the pop-up.
  4. Stylus will open a code editor in a new browser tab. Copy-paste my scroll snap user styles into the editor and press the Save button in the sidebar on the left. The styles will be applied to your Twitter timeline immediately (no need to reload the page).
  5. You can update the styles at any time. Click the Stylus icon and the Pencil icon to open the editor again.

Inability to override snapping

My implementation of scroll snapping for Twitter’s timeline has one major flaw. If a tweet is taller than the viewport, it is impossible to scroll the page to reveal the bottom part of that tweet (e.g., if you want to like or retweet that tweet) because the browser forcefully snaps the page to show the top of the tweet (or the top of the following tweet).

The severity of this problem depends on the user’s display. Viewing Twitter’s timeline on a large desktop monitor at a small page zoom factor, you may not encounter any tweets taller than the viewport.

I have asked the CSS Working Group if it would be possible to add a mechanism allowing the user to override the browser’s mandatory scroll snapping. I should probably mention that this problem could, at least in theory, be resolved by switching from mandatory to proximity snapping. I’ve tested proximity snapping in Chrome and Firefox, and I found it inconsistent and confusing. The browser would often snap when I didn’t expect it to, and vice versa. Maybe Twitter’s code is interfering with the proximity algorithm, the browsers are still a bit buggy, or perhaps I’m just “scrolling it wrong,” if that’s even possible. I don’t know.

But the main reason why I went with mandatory snapping is that I wanted to avoid situations where the topmost tweet is only partially on screen after a scroll. The type of fast-scrolling between sets of tweets that I’ve shown in the video above is only possible with mandatory snapping.

If you, like me, prefer mandatory snapping, I can suggest the following two workarounds for the “tall tweet” problem:

  • You can open the tweet on its own page and return to the timeline afterward.
  • If you only want to click the Like or Retweet buttons, you can Shift-click the tweet to select it and then press L to like it, or T followed by Enter to retweet it.

Issues in Chrome and Safari

My scroll snap user styles produce noticeably different scroll snapping behaviors in Chrome, Safari, and Firefox. Those differences are in part since the exact implementation of the snapping mechanism is left up to the browser:

The CSS Scroll Snap Module intentionally does not specify nor mandate any precise animations or physics used to enforce snap positions; this is left up to the user agent.

The current version of Safari has a bug that prevents scroll snapping from working correctly on the Twitter timeline. I have reported this bug.

In Chrome, I have encountered the following problems:

  • The scrolling operations animate inconsistently. Sometimes the animation is slow, sometimes it’s instant, and sometimes it starts slow but is then cut short. I found this irritating.
  • The scrolling operations animate too slowly in general. I performed a test in Chrome and Firefox (20 Space presses), and it took me 70% more time to cover the same distance on my Twitter timeline in Chrome than in Firefox (18.5 seconds in Chrome vs. 11 seconds in Firefox).
  • When I scroll using my laptop’s trackpad, the page flickers a lot. When I attempt to scroll fast by holding down the Space key, the page scrolls very slowly and oscillates. I suspect that both issues are caused by the same algorithm. It seems that Chrome re-snaps at a very high rate in these cases. I have reported this bug.

These browser bugs and differences between browsers can be a problem for websites considering implementing scroll snapping. For example, a web developer might hold back because they don’t like how scroll snapping behaves in one particular browser. Browsers can mitigate this problem by becoming more interoperable. In fact, Scroll Snap is one of the areas of focus of the cross-browser Interop 2022 effort.

Another way the situation could be improved is by introducing new CSS properties that would make scroll snapping more configurable. This could include the duration of the snapping animation, the length of the proximity threshold for snapping, and a mechanism to override mandatory snapping.

To snap or not to snap?

I’ve been using my scroll snap user styles on Twitter’s timeline for a couple of weeks, and I don’t want to go back. The ability to quickly flip through my feed with only the Space key is just on another level.

However, I consider this an advanced feature that probably isn’t for everyone. There’s a reason why I’ve enabled it only on the timeline (/home path) and nowhere else on Twitter’s website. Snapping is a significant change in how the page scrolls, and it takes some time to get used to. It can work great for a specific use case, but it can also get in the way and frustrate the user.

Websites with feeds should therefore consider offering scroll snapping only as an optional feature, after careful consideration and plenty of testing in different browsers and with different input methods (mouse, keyboard, trackpad, touch screen, etc.).

Before you go…

Finally, I highly recommend installing and trying out the Stylus browser extension. Web developers (or anyone who knows CSS) have the power to style any website in their browser. You can apply minor improvements and fixes to your favorite websites. I mostly use it to hide page elements that I find annoying, such as sticky headers, video pop-ups, and vote counts.

But more importantly, Stylus allows you to quickly test new CSS features on any website and report browser bugs, if necessary. By doing this, you can help make the web platform a little better.


How I Added Scroll Snapping To My Twitter Timeline originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/how-i-added-scroll-snapping-to-my-twitter-timeline/

Actually, the San Francisco Typeface Does Ship as a Variable Font

Apple unveiled an expanded version of its San Francisco system font at WWDC 2022. Then, last month, Jim Nielsen zeroed in on the font’s variations, explaining how the font provides a spectrum of variations based on the width and weight. It’s a remarkable read if you haven’t checked it.

With all of these great new options, you might be tempted to use them in a web design. Chris was ogling over the expanded sets as well over on his personal blog and pondered:

But it’s not year clear how we might tap into the condensed, compressed, and expanded varieties in CSS, or if there is even a plan to allow that. I suppose we can peek around Apple.com eventually and see how they do it if they start using them there.

Doesn’t this make perfect sense to construct as a variable font and ship the whole kit and kaboodle that way?

Turns out, yes. It does make perfect sense. Chris follows up in a new post:

But just yesterday I randomly stumbled across the fact that the built-in San Francisco font (on the Apple devices that have it built-in) is already variable (!!). See, I was derping around with Roboto Flex, and had system-ui as the fallback font, and I was noticing that during the FOUT, the font-variation-settings I was using had an effect on the fallback font, which renders as San Francisco on my Mac. Which… unless I’m daft… means that San Francisco is a variable font.

So, as for using it? Chris has a demo, of course:

There are some gotchas to all this, the most significant being fallbacks for non-Apple devices. After all, that demo is simply calling system-ui for the font family — it’s not telling the browser to download a font file or anything and who knows if Apple is gonna ever ship a variable font file we can serve up as an actual custom web font.

The other interesting thing? Chris did some sleuthing and counted 35 layout featured included in that system font. Go read the rest of the post to see ’em all (and to get a good ol’ dose of Chris-isms — I know I miss them!).

To Shared LinkPermalink on CSS-Tricks


Actually, the San Francisco Typeface Does Ship as a Variable Font originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/actually-the-san-francisco-typeface-does-ship-as-a-variable-font/

Thursday, August 4, 2022

Converting Speech to PDF with NextJS and ExpressJS

With speech interfaces becoming more of a thing, it’s worth exploring some of the things we can do with speech interactions. Like, what if we could say something and have that transcribed and pumped out as a downloadable PDF?

Well, spoiler alert: we absolutely can do that! There are libraries and frameworks we can cobble together to make it happen, and that’s what we’re going to do together in this article.

These are the tools we‘re using

First off, these are the two big players: Next.js and Express.js.

Next.js tacks on additional functionalities to React, including key features for building static sites. It’s a go-to for many developers because of what it offers right out of the box, like dynamic routing, image optimization, built-in-domain and subdomain routing, fast refreshes, file system routing, and API routes… among many, many other things.

In our case, we definitely need Next.js for its API routes on our client server. We want a route that takes a text file, converts it to PDF, writes it to our filesystem, then sends a response to the client.

Express.js allows us to get a little Node.js app going with routing, HTTP helpers, and templating. It’s a server for our own API, which is what we’ll need as we pass and parse data between things.

We have some other dependencies we’ll be putting to use:

  1. react-speech-recognition: A library for converting speech to text, making it available to React components.
  2. regenerator-runtime: A library for troubleshooting the “regeneratorRuntime is not defined” error that shows up in Next.js when using react-speech-recognition
  3. html-pdf-node: A library for converting an HTML page or public URL into a PDF
  4. axios: A library for making HTTP requests in both the browser and Node.js
  5. cors: A library that allows cross-origin resource sharing

Setting up

The first thing we want to do is create two project folders, one for the client and one for the server. Name them whatever you’d like. I’m naming mine audio-to-pdf-client and audio-to-pdf-server, respectively.

The fastest way to get started with Next.js on the client side is to bootstrap it with create-next-app. So, open your terminal and run the following command from your client project folder:

npx create-next-app client

Now we need our Express server. We can get it by cd-ing into the server project folder and running the npm init command. A package.json file will be created in the server project folder once it’s done.

We still need to actually install Express, so let’s do that now with npm install express. Now we can create a new index.js file in the server project folder and drop this code in there:

const express = require("express")
const app = express()

app.listen(4000, () => console.log("Server is running on port 4000"))

Ready to run the server?

node index.js

We’re going to need a couple more folders and and another file to move forward:

  • Create a components folder in the client project folder.
  • Create a SpeechToText.jsx file in the components subfolder.

Before we go any further, we have a little cleanup to do. Specifically, we need to replace the default code in the pages/index.js file with this:

import Head from "next/head";
import SpeechToText from "../components/SpeechToText";

export default function Home() {
  return (
    <div className="home">
      <Head>
        <title>Audio To PDF</title>
        <meta
          name="description"
          content="An app that converts audio to pdf in the browser"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <h1>Convert your speech to pdf</h1>

      <main>
        <SpeechToText />
      </main>
    </div>
  );
}

The imported SpeechToText component will eventually be exported from components/SpeechToText.jsx.

Let’s install the other dependencies

Alright, we have the initial setup for our app out of the way. Now we can install the libraries that handle the data that’s passed around.

We can install our client dependencies with:

npm install react-speech-recognition regenerator-runtime axios

Our Express server dependencies are up next, so let’s cd into the server project folder and install those:

npm install html-pdf-node cors

Probably a good time to pause and make sure the files in our project folders are in tact. Here’s what you should have in the client project folder at this point:

/audio-to-pdf-web-client
├─ /components
|  └── SpeechToText.jsx
├─ /pages
|  ├─ _app.js
|  └── index.js
└── /styles
    ├─globals.css
    └── Home.module.css

And here’s what you should have in the server project folder:

/audio-to-pdf-server
└── index.js

Building the UI

Well, our speech-to-PDF wouldn’t be all that great if there’s no way to interact with it, so let’s make a React component for it that we can call <SpeechToText>.

You can totally use your own markup. Here’s what I’ve got to give you an idea of the pieces we’re putting together:

import React from "react";

const SpeechToText = () => {
  return (
    <>
      <section>
        <div className="button-container">
          <button type="button" style=--bgColor>
            Start
          </button>
          <button type="button" style=--bgColor>
            Stop
          </button>
        </div>
        <div
          className="words"
          contentEditable
          suppressContentEditableWarning={true}
        ></div>
        <div className="button-container">
          <button type="button" style=--bgColor>
            Reset
          </button>
          <button type="button" style=--bgColor>
            Convert to pdf
          </button>
        </div>
      </section>
    </>
  );
};

export default SpeechToText;

This component returns a React fragment that contains an HTML <``section``> element that contains three divs:

  • .button-container contains two buttons that will be used to start and stop speech recognition.
  • .words has contentEditable and suppressContentEditableWarning attributes to make this element editable and suppress any warnings from React.
  • Another .button-container holds two more buttons that will be used to reset and convert speech to PDF, respectively.

Styling is another thing altogether. I won’t go into it here, but you’re welcome to use some styles I wrote either as a starting point for your own styles/global.css file.

View Full CSS
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

.home {
  background-color: #333;
  min-height: 100%;
  padding: 0 1rem;
  padding-bottom: 3rem;
}

h1 {
  width: 100%;
  max-width: 400px;
  margin: auto;
  padding: 2rem 0;
  text-align: center;
  text-transform: capitalize;
  color: white;
  font-size: 1rem;
}

.button-container {
  text-align: center;
  display: flex;
  justify-content: center;
  gap: 3rem;
}

button {
  color: white;
  background-color: var(--bgColor);
  font-size: 1.2rem;
  padding: 0.5rem 1.5rem;
  border: none;
  border-radius: 20px;
  cursor: pointer;
}

button:hover {
  opacity: 0.9;
}

button:active {
  transform: scale(0.99);
}

.words {
  max-width: 700px;
  margin: 50px auto;
  height: 50vh;
  border-radius: 5px;
  padding: 1rem 2rem 1rem 5rem;
  background-image: -webkit-gradient(
    linear,
    0 0,
    0 100%,
    from(#d9eaf3),
    color-stop(4%, #fff)
  ) 0 4px;
  background-size: 100% 3rem;
  background-attachment: scroll;
  position: relative;
  line-height: 3rem;
  overflow-y: auto;
}

.success,
.error {
  background-color: #fff;
  margin: 1rem auto;
  padding: 0.5rem 1rem;
  border-radius: 5px;
  width: max-content;
  text-align: center;
  display: block;
}

.success {
  color: green;
}

.error {
  color: red;
}

The CSS variables in there are being used to control the background color of the buttons.

Let’s see the latest changes! Run npm run dev in the terminal and check them out.

You should see this in browser when you visit http://localhost:3000:

Our first speech to text conversion!

The first action to take is to import the necessary dependencies into our <SpeechToText> component:

import React, { useRef, useState } from "react";
import SpeechRecognition, {
  useSpeechRecognition,
} from "react-speech-recognition";
import axios from "axios";

Then we check if speech recognition is supported by the browser and render a notice if not supported:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

if (!speechRecognitionSupported) {
  return <div>Your browser does not support speech recognition.</div>;
}

Next up, let’s extract transcript and resetTranscript from the useSpeechRecognition() hook:

const { transcript, resetTranscript } = useSpeechRecognition();

This is what we need for the state that handles listening:

const [listening, setListening] = useState(false);

We also need a ref for the div with the contentEditable attribute, then we need to add the ref attribute to it and pass transcript as children:

const textBodyRef = useRef(null);

…and:

<div
  className="words"
  contentEditable
  ref={textBodyRef}
  suppressContentEditableWarning={true}
  >
  {transcript}
</div>

The last thing we need here is a function that triggers speech recognition and to tie that function to the onClick event listener of our button. The button sets listening to true and makes it run continuously. We’ll disable the button while it’s in that state to prevent us from firing off additional events.

const startListening = () => {
  setListening(true);
  SpeechRecognition.startListening({
    continuous: true,
  });
};

…and:

<button
  type="button"
  onClick={startListening}
  style=--bgColor
  disabled={listening}
>
  Start
</button>

Clicking on the button should now start up the transcription.

More functions

OK, so we have a component that can start listening. But now we need it to do a few other things as well, like stopListening, resetText and handleConversion. Let’s make those functions.

const stopListening = () => {
  setListening(false);
  SpeechRecognition.stopListening();
};

const resetText = () => {
  stopListening();
  resetTranscript();
  textBodyRef.current.innerText = "";
};

const handleConversion = async () => {}

Each of the functions will be added to an onClick event listener on the appropriate buttons:

<button
  type="button"
  onClick={stopListening}
  style=--bgColor
  disabled={listening === false}
>
  Stop
</button>

<div className="button-container">
  <button
    type="button"
    onClick={resetText}
    style=--bgColor
  >
    Reset
  </button>
  <button
    type="button"
    style=--bgColor
    onClick={handleConversion}
  >
    Convert to pdf
  </button>
</div>

The handleConversion function is asynchronous because we will eventually be making an API request. The “Stop” button has the disabled attribute that would be be triggered when listening is false.

If we restart the server and refresh the browser, we can now start, stop, and reset our speech transcription in the browser.

Now what we need is for the app to transcribe that recognized speech by converting it to a PDF file. For that, we need the server-side path from Express.js.

Setting up the API route

The purpose of this route is to take a text file, convert it to a PDF, write that PDF to our filesystem, then send a response to the client.

To setup, we would open the server/index.js file and import the html-pdf-node and fs dependencies that will be used to write and open our filesystem.

const HTMLToPDF = require("html-pdf-node");
const fs = require("fs");
const cors = require("cors)

Next, we will setup our route:

app.use(cors())
app.use(express.json())

app.post("/", (req, res) => {
  // etc.
})

We then proceed to define our options required in order to use html-pdf-node inside the route:

let options = { format: "A4" };
let file = {
  content: `<html><body><pre style='font-size: 1.2rem'>${req.body.text}</pre></body></html>`,
};

The options object accepts a value to set the paper size and style. Paper sizes follow a much different system than the sizing units we typically use on the web. For example, A4 is the typical letter size.

The file object accepts either the URL of a public website or HTML markup. In order to generate our HTML page, we will use the html, body, pre HTML tags and the text from the req.body.

You can apply any styling of your choice.

Next, we will add a trycatch to handle any errors that might pop up along the way:

try {

} catch(error){
  console.log(error);
  res.status(500).send(error);
}

Next, we will use the generatePdf from the html-pdf-node library to generate a pdfBuffer (the raw PDF file) from our file and create a unique pdfName:

HTMLToPDF.generatePdf(file, options).then((pdfBuffer) => {
  // console.log("PDF Buffer:-", pdfBuffer);
  const pdfName = "./data/speech" + Date.now() + ".pdf";

  // Next code here
}

From there, we use the filesystem module to write, read and (yes, finally!) send a response to the client app:

fs.writeFile(pdfName, pdfBuffer, function (writeError) {
  if (writeError) {
    return res
      .status(500)
      .json({ message: "Unable to write file. Try again." });
  }

  fs.readFile(pdfName, function (readError, readData) {
    if (!readError && readData) {
      // console.log({ readData });
      res.setHeader("Content-Type", "application/pdf");
      res.setHeader("Content-Disposition", "attachment");
      res.send(readData);
      return;
    }

    return res
      .status(500)
      .json({ message: "Unable to write file. Try again." });
  });
});

Let’s break that down a bit:

  • The writeFile filesystem module accepts a file name, data and a callback function that can returns an error message if there’s an issue writing to the file. If you’re working with a CDN that provides error endpoints, you could use those instead.
  • The readFile filesystem module accepts a file name and a callback function that is capable or returning a read error as well as the read data. Once we have no read error and the read data is present, we will construct and send a response to the client. Again, this can be replaced with your CDN’s endpoints if you have them.
  • The res.setHeader("Content-Type", "application/pdf"); tells the browser that we are sending a PDF file.
  • The res.setHeader("Content-Disposition", "attachment"); tells the browser to make the received data downloadable.

Since the API route ready, we can use it in our app at http://localhost:4000. We can the proceed to the client part of our application to complete the handleConversion function.

Handling the conversion

Before we can start working on a handleConversion function, we need to create a state that handles our API requests for loading, error, success, and other messages. We’re going use React’s useState hook to set that up:

const [response, setResponse] = useState({
  loading: false,
  message: "",
  error: false,
  success: false,
});

In the handleConversion function, we will check for when the web page has been loaded before running our code and make sure the div with the editable attribute is not empty:

if (typeof window !== "undefined") {
const userText = textBodyRef.current.innerText;
  // console.log(textBodyRef.current.innerText);

  if (!userText) {
    alert("Please speak or write some text.");
    return;
  }
}

We proceed by wrapping our eventual API request in a trycatch, handling any error that may arise, and updating the response state:

try {

} catch(error){
  setResponse({
    ...response,
    loading: false,
    error: true,
    message:
      "An unexpected error occurred. Text not converted. Please try again",
    success: false,
  });
}

Next, we set some values for the response state and also set config for axios and make a post request to the server:

setResponse({
  ...response,
  loading: true,
  message: "",
  error: false,
  success: false,
});
const config = {
  headers: {
    "Content-Type": "application/json",
  },
  responseType: "blob",
};

const res = await axios.post(
  "http://localhost:4000",
  {
    text: textBodyRef.current.innerText,
  },
  config
);

Once we have gotten a successful response, we set the response state with the appropriate values and instruct the browser to download the received PDF:

setResponse({
  ...response,
  loading: false,
  error: false,
  message:
    "Conversion was successful. Your download will start soon...",
  success: true,
});

// convert the received data to a file
const url = window.URL.createObjectURL(new Blob([res.data]));
// create an anchor element
const link = document.createElement("a");
// set the href of the created anchor element
link.href = url;
// add the download attribute, give the downloaded file a name
link.setAttribute("download", "yourfile.pdf");
// add the created anchor tag to the DOM
document.body.appendChild(link);
// force a click on the link to start a simulated download
link.click();

And we can use the following below the contentEditable div for displaying messages:

<div>
  {response.success && <i className="success">{response.message}</i>}
  {response.error && <i className="error">{response.message}</i>}
</div>

Final code

I’ve packaged everything up on GitHub so you can check out the full source code for both the server and the client.


Converting Speech to PDF with NextJS and ExpressJS originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/converting-speech-to-pdf-with-nextjs-and-expressjs/

Tuesday, August 2, 2022

Implicit Grids, Repeatable Layout Patterns, and Danglers

Dave Rupert with some modern CSS magic that tackles one of those classic conundrums: what happens when the CSS for component is unable to handle the content we throw at it?

The specific situation is when a layout grid expects an even number of items, but is supplied with an odd number instead. We’re left with a “dangling” element at the end that throws off the layout. Sounds like what’s needed is some Defensive CSS and Dave accomplishes it.

He reaches for :has() to write a nifty selector that sniffs out the last item in a grid that contains an odd number of items:

.items:has(.item:last-of-type:nth-of-type(odd)) .item:first-of-type { }

Breaking that down:

  • We have a parent container of .items.
  • If the container :has() an .item child that is the last of its type,
  • …and that .item happens to be an odd-numbered instance,
  • …then select the first .item element of that type and style it!

In this case, that last .item can be set to go full-width to prevent holes in the layout.

If… then… CSS has conditional logic powers! We’re only talking about support for Safari TP and Edge/Chrome Canary at the moment, but that’s pretty awesome.

As chance has it, Temani Afif recently shared tricks he learned while experimenting with implicit grids. By taking advantage of CSS Grid’s auto-placement algorithm, we don’t even have to explicitly declare a fixed number of columns and rows for a grid — CSS will create them for us if they’re needed!

No, Temani’s techniques aren’t alternative solutions to Dave’s “dangler” dilemma. But combining Temani’s approach to repeatable grid layout patterns with Dave’s defensive CSS use of :has(), we get a pretty powerful and complex-looking grid that’s lightweight and capable of handling any number of items while maintaining a balanced, repeatable pattern.


Implicit Grids, Repeatable Layout Patterns, and Danglers originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/implicit-grids-repeatable-layout-patterns-and-danglers/

Monday, August 1, 2022

Exploring CSS Grid’s Implicit Grid and Auto-Placement Powers

When working with CSS Grid, the first thing to do is to set display: grid on the element that we want to be become a grid container. Then we explicitly define the grid using a combination of grid-template-columns, grid-template-rows, and grid-template-areas. And from there, the next step is to place items inside the grid.

This is the classic approach that should be used and I also recommend it. However, there is another approach for creating grids without any explicit definition. We call this the implicit grid.

“Explicit, implicit? What the heck is going on here?”

Strange terms, right? Manuel Matuzovic already has a good explanation of what we may by “implicit” and “explicit” in CSS Grid, but let’s dig straight into what the specification says:

The grid-template-rows, grid-template-columns, and grid-template-areas properties define a fixed number of tracks that form the explicit grid. When grid items are positioned outside of these bounds, the grid container generates implicit grid tracks by adding implicit grid lines to the grid. These lines together with the explicit grid form the implicit grid.

So, in plain English, the browser auto-generates extra rows and columns in case any elements happen to be placed outside the defined grid.

What about auto-placement?

Similar to the concept of implicit grid, auto-placement is the ability of the browser to automatically place the items inside the grid. We don’t always need to give the position of each item.

Through different use cases, we are going to see how such features can help us create complex and dynamic grid with a few lines of code.

Dynamic sidebar

Here, we have three different layouts but we only have one grid configuration that works for all of them.

main {
  display: grid;
  grid-template-columns: 1fr;
}

Only one column is taking up all the free space. This is our “explicit” grid. It’s set up to fit one grid item in the main grid container. That’s all. One column and one row:

But what if we decided to drop another element in there, say an aside (our dynamic sidebar). As it’s currently (and explicitly) defined, our grid will have to adjust automatically to find a place for that element. And if we do nothing else with our CSS, here’s what DevTools tells us is happening.

The element takes up the entire column that is explicitly set on the container. Meanwhile, the falls onto a new row between implicit grid lines labeled 2 and 3. Note that I’m using a 20px gap to help separate things visually.

We can move the <aside> to a column beside the <section>:

aside {
  grid-column-start: 2;
}

And here’s what DevTools tells us now:

The element is between the grid container’s first and second grid column lines. The starts at the second grid column line and ends at a third line we never declared.

We place our element in the second column but… we don’t have a second column. Weird, right? We never declared a second column on the <main> grid container, but the browser created one for us! This is the key part from the specification we looked at:

When grid items are positioned outside of these bounds, the grid container generates implicit grid tracks by adding implicit grid lines to the grid.

This powerful feature allows us to have dynamic layouts. If we only have the <section> element, all we get is one column. But if we add an <aside> element to the mix, an extra column is created to contain it.

We could place the <aside> before the <section> instead like this:

aside {
  grid-column-end: -2;
} 

This creates the implicit column at the start of the grid, unlike the previous code that places the implicit column at the end.

We can have either a right or left sidebar

We can do the same thing more easily using the grid-auto-flow property to set any and all implicit tracks to flow in a column direction:

Now there’s no need to specify grid-column-start to place the <aside> element to the right of the <section>! In fact, any other grid item we decide to throw in there at any time will now flow in a column direction, each one placed in its own implicit grid tracks. Perfect for situations where the number of items in the grid isn’t known in advance!

That said, we do still need grid-column-end if we want to place it in a column to the left of it because, otherwise, the <aside> will occupy the explicit column which, in turn, pushes the <section> outside the explicit grid and forces it to take the implicit column.

I know, I know. That’s a little convoluted. Here is another example we can use to better understand this little quirk:

In the first example, we didn’t specify any placement. In this case, the browser will first place the <aside> element in the explicit column since it comes first in the DOM. The <section>, meanwhile, is automatically placed in the grid column the browser automatically (or implicitly) creates for us.

In the second example, we set the <aside> element outside of the explicit grid:

aside {
  grid-column-end: -2;
}

Now it doesn’t matter that <aside> comes first in the HTML. By reassigning <aside> somewhere else, we’ve made the <section> element available to take the explicit column.

Image grid

Let’s try something different with a grid of images where we have a big image and a few thumbnails beside it (or under it).

We have two grid configurations. But guess what? I am not defining any grid at all! All I am doing is this:

.grid img:first-child {
  grid-area: span 3 / span 3;
}

It’s surprising we only need one line of code to pull off something like this, so let’s dissect what’s going on and you will see that it’s easier than you may think. First of all, grid-area is a shorthand property that combines the following properties into a single declaration:

  • grid-row-start
  • grid-row-end
  • grid-column-start
  • grid-column-end

Wait! Isn’t grid-area the property we use to define named areas instead of where elements start and end on the grid?

Yes, but it also does more. We could write a whole lot more about grid-area, but in this particular case:

.grid img:first-child {
  grid-area: span 3 / span 3;
}

/* ...is equivalent to: */
.grid img:first-child {
  grid-row-start: span 3;
  grid-column-start: span 3;
  grid-row-end: auto;
  grid-column-end: auto;
}

We can see the same thing when cracking open DevTools to expand the shorthand version:

This means that the first image element in the grid needs to span three columns and three rows. But since we didn’t define any columns or rows, the browser does it for us.

We’ve essentially placed the first image in the HTML to take up a 3⨉3 grid. That means that any other images will be placed automatically in those same three columns without the need to specify anything new.

To summarize, we told the browser that the first image needs take up the space of three columns and three rows that we never explicitly defined when setting up the grid container. The browser set those columns and rows up for us. As a result, the remaining images in the HTML flow right into place using the same three columns and rows. And since the first image takes up all three columns in the first row, the remaining images flow into additional rows that each contain three columns, where each image takes up a single column.

All this from one line of CSS! That’s the power of “implicit” grid” and auto-placement.

For the second grid configuration in that demo, all I’ve done is change the automatic flow direction using grid-auto-flow: column the same way we did earlier when placing an <aside> element next to a <section>. This forces the browser to create a fourth column it can use to place the remaining images. And since we have three rows, the remaining images get placed inside the same vertical column.

We need to add a few properties to the images to make sure they fit nicely inside the grid without any overflow:

.grid {
  display: grid;
  grid-gap: 10px;
}

/* for the second grid configuration */
.horizontal {
  grid-auto-flow: column;
}

/* The large 3⨉3 image */
.grid img:first-child {
  grid-area: span 3 / span 3;
}

/* Help prevent stretched or distorted images */
img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

And of course, we can easily update the grid to consider more images by adjusting one value. That would be the 3 in the styles for the large image. We have this:

.grid img:first-child {
  grid-area: span 3 / span 3;
}

But we could add a fourth column simply by changing it to 4 instead:

.grid img:first-child {
  grid-area: span 4 / span 4;
}

Even better: let’s set that up as a custom property to make things even easier to update.

Dynamic layouts

The first use case with the sidebar was our first dynamic layout. Now we will tackle more complex layouts where the number of elements will dictate the grid configuration.

In this example, we can have anywhere from one to four elements where the grid adjusts in way that nicely fits the number of elements without leaving any awkward gaps or missing spaces.

When we have one element, we do nothing. The element will stretch to fill the only row and column automatically created by the grid.

Bit when we add the second element, we create another (implicit) column using grid-column-start: 2.

When we add a third element, it should take up the width of two columns — that’s why we used grid-column-start: span 2, but only if it’s the :last-child because if (and when) we add a fourth element, that one should only take up a single column.

Adding that up, we have four grid configurations with only two declarations and the magic of implicit grid:

.grid {
  display: grid;
}
.grid :nth-child(2) {
  grid-column-start: 2;
}
.grid :nth-child(3):last-child {
  grid-column-start: span 2;
}

Let’s try another one:

We’re doing nothing for the first and second cases where we have only one or two elements. When we add a third element, though, we tell the browser that — as long as it’s the :last-child — it should span two columns. When we add a fourth element, we tell the browser that element needs to be placed in the second column.

.grid {
  display: grid;
}
.grid :nth-child(3):last-child {
  grid-column-start: span 2;
}
.grid :nth-child(4) {
  grid-column-start: 2;
}

Are you starting to get the trick? We give the browser specific instructions based on the number of elements (using :nth-child) and, sometimes, one instruction can change the layout completely.

It should be noted that the sizing will not be the same when we work with different content:

Since we didn’t define any sizes for our items, the browser automatically sizes them for us based on their contents and we may end up with different sizing than what we just saw. To overcome this, we have to explicitly specify that all the columns and rows are equally sized:

grid-auto-rows: 1fr;
grid-auto-columns: 1fr;

Hey, we haven’t played with those properties yet! grid-auto-rows and grid-auto-columns set the size of implicit rows and columns, respectively, in a grid container. Or, as the spec explains it:

The grid-auto-columns and grid-auto-rows properties specify the size of tracks not assigned a size by grid-template-rows or grid-template-columns.

Here is another example where we can go up to six elements. This time I will let you dissect the code. Don’t worry, the selectors may look complex but the logic is pretty straightforward.

Even with six elements, we only needed two declarations. Imagine all the complex and dynamic layouts we can achieve with a few lines of code!

What’s going on with that grid-auto-rows and why does it take three values? Are we defining three rows?

No, we are not defining three rows. But we are defining three values as a pattern for our implicit rows. The logic is as follows:

  • If we have one row, it will get sized with the first value.
  • If we have two rows, the first one gets the first value and the second one the second value.
  • If we have three rows, the three values will get used.
  • If we have four rows (and here comes the interesting part), we use the three values for the first three rows and we reuse the first value again for the fourth row. That’s why it’s a kind of pattern that we repeat to size all the implicit rows.
  • If we have 100 rows, they will be sized three-by-three to have 2fr 2fr 1fr 2fr 2fr 1fr 2fr 2fr 1fr, etc.

Unlike grid-template-rows which defines the number of rows and their sizes, grid-auto-rows only sizes row that may get created along the way.

If we get back to our example, the logic is to have equal size when two rows are created (we will use the 2fr 2fr), but if a third row is created we make it a bit smaller.

Grid patterns

For this last one, we are going to talk about patterns. You have probably seen those two column layouts where one column is wider than the other, and each row alternates the placement of those columns.

This sort layout can be difficult too pull off without knowing exactly how much content we’re dealing with, but CSS Grid’s implicit auto-placement powers makes it a relative cinch.

Take a peek at the code. It may look complex but let’s break it down because it winds up being pretty straightforward.

The first thing to do is to identify the pattern. Ask yourself: “After how many elements should the pattern repeat?” In this case it’s after every four elements. So, let’s look at using only four elements for now:

Now, let’s define the grid and set up the general pattern using the :nth-child selector for alternating between elements:

.grid {
  display: grid;
  grid-auto-columns: 1fr; /* all the columns are equal */
  grid-auto-rows: 100px; /* all the rows equal to 100px */
}
.grid :nth-child(4n + 1) { /* ?? */ }
.grid :nth-child(4n + 2) { /* ?? */ }
.grid :nth-child(4n + 3) { /* ?? */ }
.grid :nth-child(4n + 4) { /* ?? */ }

We said that our pattern repeats every four elements, so we will logically use 4n + x where x ranges from 1 to 4. It’s a little easier to explain the pattern this way:

4(0) + 1 = 1 = 1st element /* we start with n = 0 */
4(0) + 2 = 2 = 2nd element
4(0) + 3 = 3 = 3rd element
4(0) + 4 = 4 = 4th element
4(1) + 1 = 5 = 5th element /* our pattern repeat here at n = 1 */
4(1) + 2 = 6 = 6th element
4(1) + 3 = 7 = 7th element
4(1) + 4 = 8 = 8th element
4(2) + 1 = 9 = 9th element /* our pattern repeat again here at n = 2 */
etc.

Perfect, right? We have four elements, and repeat the pattern on the fifth element, the ninth element and so on.

Those :nth-child selectors can be tricky! Chris has a super helpful explanation of how it all works, including recipes for creating different patterns.

Now we configure each element so that:

  1. The first element needs to take two columns and start at column one (grid-column: 1/span 2).
  2. The second element is placed in the third column (grid-column-start: 3).
  3. The third element is placed at the first column: (grid-column-start: 1).
  4. The fourth element takes two columns and starts at the second column: (grid-column: 2/span 2).

Here that is in CSS:

.grid {
  display: grid;
  grid-auto-columns: 1fr; /* all the columns are equal */
  grid-auto-rows: 100px; /* all the rows are equal to 100px */
}
.grid :nth-child(4n + 1) { grid-column: 1/span 2; }
.grid :nth-child(4n + 2) { grid-column-start: 3; }
.grid :nth-child(4n + 3) { grid-column-start: 1; }
.grid :nth-child(4n + 4) { grid-column: 2/span 2; }

We could stop here and be done… but we can do better! Specifically, we can remove some declarations and rely grid’s auto-placement powers to do the job for us. This is the trickiest part to grok and requires a lot of practice to be able to identify what can be removed.

The first thing we can do is update grid-column: 1 /span 2 and use only grid-column: span 2 since, by default, the browser will place the first item into the first column. We can also remove this:

.grid :nth-child(4n + 3) { grid-column-start: 1; }

By placing the first, second, and fourth items, the grid automatically places the third item in the correct place. That means we’re left with this:

.grid {
  display: grid;
  grid-auto-rows: 100px; /* all the rows are equal to 100px */
  grid-auto-columns: 1fr; /* all the columns are equal */
}
.grid :nth-child(4n + 1) { grid-column: span 2; }
.grid :nth-child(4n + 2) { grid-column-start: 2; }
.grid :nth-child(4n + 4) { grid-column: 2/span 2; }

But c’mon we can stroll do better! We can also remove this:

.grid :nth-child(4n + 2) { grid-column-start: 2; }

Why? If we place the fourth element in the second column while allowing it to take up two full columns, we’re forcing the grid to create a third implicit column, giving us a total of three columns without explicitly telling it to. The fourth element cannot go into the first row since the first item is also taking two columns, so it flows to the next row. This configuration leave us with an empty column in the first row and an empty one in the second row.

I think you know the end of the story. The browser will automatically place the second and third items in those empty spots. So our code becomes even simpler:

.grid {
  display: grid;
  grid-auto-columns: 1fr; /* all the columns are equal */
  grid-auto-rows: 100px; /* all the rows are equal to 100px */
}
.grid :nth-child(4n + 1) { grid-column: span 2; }
.grid :nth-child(4n + 4) { grid-column: 2/span 2; }

All it takes is five declarations to create a very cool and very flexible pattern. The optimization part may be tricky, but you get used to it and gain some tricks with practice.

Why not use grid-template-columns to define explicit columns since we know the number of columns?

We can do that! Here’s the code for it:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* all the columns are equal */
  grid-auto-rows: 100px; /* all the rows are equal to 100px */
}
.grid :nth-child(4n + 1),
.grid :nth-child(4n + 4) {
  grid-column: span 2;
}

As you can see, the code is definitely more intuitive. We define three explicit grid columns and we tell the browser that the first and fourth elements need to take two columns. I highly recommend this approach! But the goal of this article is to explore new ideas and tricks that we get from CSS Grid’s implicit and auto-placement powers.

The explicit approach is more straightforward, while an implicit grid requires you to — pardon the pun — fill in the gaps where CSS is doing additional work behind the scenes. In the end, I believe that having a solid understanding of implicit grids will help you better understand the CSS Grid algorithm. After all, we are not here to study what’s obvious — we are here to explore wild territories!

Let’s try another pattern, a bit quicker this time:

Our pattern repeats every six elements. The third and fourth elements each need to occupy two full rows. If we place the third and the fourth elements, it seems that we don’t need to touch the others, so let’s try the following:

.grid {
  display: grid;
  grid-auto-columns: 1fr;
  grid-auto-rows: 100px;
}
.grid :nth-child(6n + 3) {
  grid-area: span 2/2; /* grid-row-start: span 2 && grid-column-start: 2 */
}
.grid :nth-child(6n + 4) {
  grid-area: span 2/1; /* grid-row-start: span 2 && grid-column-start: 1 */
}

Hmm, no good. We need to place the second element in the first column. Otherwise, the grid will automatically place it in the second column.

.grid :nth-child(6n + 2) {
  grid-column: 1; /* grid-column-start: 1 */
}

Better, but there’s still more work, We need to shift the third element to the top. It’s tempting to try placing it in the first row this way:

.grid :nth-child(6n + 3) {
  grid-area: 1/2/span 2; 
    /* Equivalent to:
       grid-row-start: 1;
       grid-row-end: span 2;
       grid-column-start: 2 
     */
}

But this doesn’t work because it forces all the 6n + 3 elements to get placed in the same area which makes a jumbled layout. The real solution is to keep the initial definition of the third element and add grid-auto-flow: dense to fill the gaps. From MDN:

[The] “dense” packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. This may cause items to appear out-of-order, when doing so would fill in holes left by larger items. If it is omitted, a “sparse” algorithm is used, where the placement algorithm only ever moves “forward” in the grid when placing items, never backtracking to fill holes. This ensures that all of the auto-placed items appear “in order”, even if this leaves holes that could have been filled by later items.

I know this property is not very intuitive but never forget it when you face a placement issue. Before trying different configurations in vain, add it because it may fix your layout with no additional effort.

Why not always add this property by default?

I don’t recommend it because, in some cases, we don’t want that behavior. Note how the MDN’s explanation there mentions it causes items to flow “out-of-order” to fill holes left by larger items. Visual order is usually just as important as the source order, particularly when it comes to accessible interfaces, and grid-auto-flow: dense can sometimes cause a mismatch between the visual and source order.

Our final code is then:

.grid {
  display: grid;
  grid-auto-columns: 1fr;
  grid-auto-flow: dense;
  grid-auto-rows: 100px;
}
.grid :nth-child(6n + 2) { grid-column: 1; }
.grid :nth-child(6n + 3) { grid-area: span 2/2; }
.grid :nth-child(6n + 4) { grid-row: span 2; }

Another one? Let’s go!

For this one, I will not talk too much and instead show you an illustration of the code I have used. Try to see if you get how I reached that code:

The items in black are implicitly placed in the grid. It should be noted that we can get the same layout more ways than how I got there. Can you figure those out, too? What about using grid-template-columns? Share your works in the comment section.

I am gonna leave you with a last pattern:

I do have a solution for this one but it’s your turn to practice. Take all that we have learned and try to code this by yourself and then compare it with my solution. Don’t worry if you end with something verbose — the most important thing is finding a working solution.

Want more?

Before we end I want to share a few Stack Overflow questions related to CSS Grid where I jumped in with answers that use many of the techniques we covered here together. It’s a good list that shows just how many real use cases and real-world situations come up where these things come in handy:

Wrapping up

CSS Grid has been around for years, but there are still a lot of little-known and used tricks that aren’t widely discussed. The implicit grid and auto-placement features are two of them!

And yes, this can get challenging! It has taken me a lot of time to grok the logic behind implicit grids and I still struggle with auto-placement. If you want to spend more time wrapping your head around explicit and implicit grids, here are a couple of additional explanations and examples worth checking out:

Similarly, you might want to read about grid-auto-columns in the CSS-Tricks Almanac because Mojtaba Seyedi goes into great detail and includes incredibly helpful visuals to help explain the behavior.

Like I said when we started, the methods we covered here are not meant to replace the common ways you already know for building grids. I am simply exploring different ways that can be helpful in some cases.


Exploring CSS Grid’s Implicit Grid and Auto-Placement Powers originally published on CSS-Tricks. You should get the newsletter.



source https://css-tricks.com/exploring-css-grids-implicit-grid-and-auto-placement-powers/