Tag: Code

Let’s Make a QR Code Generator With a Serverless Function!

QR codes are funny, right? We love them, then hate them, then love them again. Anyways, they’ve lately been popping up again and it got me thinking about how they’re made. There are like a gazillion QR code generators out there, but say it’s something you need to do on your own website. This package can do that. But it’s also weighs in at a hefty 180 KB for everything it needs to generate stuff. You wouldn’t want to serve all that along with the rest of your scripts.

Now, I’m relatively new to the concept of cloud functions, but I hear that’s the bee’s knees for something just like this. That way, the function lives somewhere on a server that can be called when it’s needed. Sorta like a little API to run the function.

Some hosts offer some sort of cloud function feature. DigitalOcean happens to be one of them! And, like Droplets, functions are pretty easy to deploy.

Create a functions folder locally

DigitalOcean has a CLI that with a command that’ll scaffold things for us, so cd wherever you want to set things up and run:

doctl serverless init --language js qr-generator

Notice the language is explicitly declared. DigitalOcean functions also support PHP and Python.

We get a nice clean project called qr-generator with a /packages folder that holds all the project’s functions. There’s a sample function in there, but we can overlook it for now and create a qr folder right next to it:

That folder is where both the qrcode package and our qr.js function are going to live. So, let’s cd into packages/sample/qr and install the package:

npm install --save qrcode

Now we can write the function in a new qr.js file:

const qrcode = require('qrcode')  exports.main = (args) => {   return qrcode.toDataURL(args.text).then(res => ({     headers:  { 'content-type': 'text/html; charset=UTF-8' },     body: args.img == undefined ? res : `<img src="$ {res}">`   })) }  if (process.env.TEST) exports.main({text:"hello"}).then(console.log)

All that’s doing is requiring the the qrcode package and exporting a function that basically generates an <img> tag with the a base64 PNG for the source. We can even test it out in the terminal:

doctl serverless functions invoke sample/qr -p "text:css-tricks.com"

Check the config file

There is one extra step we need here. When the project was scaffolded, we got this little project.yml file and it configures the function with some information about it. This is what’s in there by default:

targetNamespace: '' parameters: {} packages:   - name: sample     environment: {}     parameters: {}     annotations: {}     actions:       - name: hello         binary: false         main: ''         runtime: 'nodejs:default'         web: true         parameters: {}         environment: {}         annotations: {}         limits: {}

See those highlighted lines? The packages: name property is where in the packages folder the function lives, which is a folder called sample in this case. The actions/ name property is the name of the function itself, which is the name of the file. It’s hello by default when we spin up the project, but we named ours qr.js, so we oughta change that line from hello to qr before moving on.

Deploy the function

We can do it straight from the command line! First, we connect to the DigitalOcean sandbox environment so we have a live URL for testing:

## You will need an DO API key handy doctl sandbox connect

Now we can deploy the function:

doctl sandbox deploy qr-generator

Once deployed, we can access the function at a URL. What’s the URL? There’s a command for that:

doctl sbx fn get sample/qr --url https://faas-nyc1-2ef2e6cc.doserverless.co/api/v1/web/fn-10a937cb-1f12-427b-aadd-f43d0b08d64a/sample/qr

Heck yeah! No more need to ship that entire package with the rest of the scripts! We can hit that URL and generate the QR code from there.

Demo

We fetch the API and that’s really all there is to it!


Let’s Make a QR Code Generator With a Serverless Function! originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,

Explain the First 10 Lines of Twitter’s Source Code to Me

For the past few weeks, I’ve been hiring for a senior full-stack JavaScript engineer at my rental furniture company, Pabio. Since we’re a remote team, we conduct our interviews on Zoom, and I’ve observed that some developers are not great at live-coding or whiteboard interviews, even if they’re good at the job. So, instead, we have an hour-long technical discussion where I ask them questions about web vitals, accessibility, the browser wars, and other similar topics about the web. One of the questions I always like to ask is: “Explain the first ten or so lines of the Twitter source code to me.”

I think it’s a simple test that tells me a lot about the depth of fundamental front-end knowledge they have, and this article lists the best answers.

For context, I share my screen, open Twitter.com and click View source. Then I ask them to go line-by-line to help me understand the HTML, and they can say as much or as little as they like. I also zoom in to make the text more legible, so you don’t see the full line but you get an idea. Here’s what it looks like:

Screenshot of source code from Twitter.

Note that since our technical discussion is a conversation. I don’t expect a perfect answer from anyone. If I hear some right keywords, I know that the candidate knows the concept, and I try to push them in the right direction.

Line 1: <!DOCTYPE html>

The first line of every document’s source code is the perfect for this interview because how much a candidate knows about the DOCTYPE declaration closely resembles how many years of experience they have. I still remember my Dreamweaver days with the long XHTML DOCTYPE line, like Chris listed in his article “The Common DOCTYPES” from 2009.

Perfect answer: This is the document type (doc-type) declaration that we always put as the first line in HTML files. You might think that this information is redundant because the browser already knows that the MIME type of the response is text/html; but in the Netscape/Internet Explorer days, browsers had the difficult task of figuring out which HTML standard to use to render the page from multiple competing versions.

This was especially annoying because each standard generated a different layout so this tag was adopted to make it easy for browsers. Previously, DOCTYPE tags were long and even included the specification link (kinda like SVGs have today), but luckily the simple <!doctype html> was standardized in HTML5 and still lives on.

Also accepted: This is the DOCTYPE tag to let the browser know that this is an HTML5 page and should be rendered as such.

Line 2: <html dir="ltr" lang="en">

This line in the source code tells me if the candidate knows about accessibility and localization. Surprisingly, only a few people knew about the dir attribute in my interviews, but it’s a great segue into a discussion about screen readers. Almost everyone was able to figure out the lang="en" attribute, even if they haven’t used it before.

Perfect answer: This is the root element of an HTML document and all other elements are inside this one. Here, it has two attributes, direction and language. The direction attribute has the value left-to-right to tell user agents which direction the content is in; other values are right-to-left for languages like Arabic, or just auto which leaves it to the browser to figure out.

The language attribute tells us that all content inside this tag is in English; you can set this value to any language tag, even to differentiate en-us and en-gb, for example. This is also useful for screen readers to know which language to announce in.

Line 3: <meta charset="utf-8">

Perfect answer: The meta tag in the source code is for supplying metadata about this document. The character set (char-set) attribute tells the browser which character encoding to use, and Twitter uses the standard UTF-8 encoding. UTF-8 is great because it has many character points so you can use all sorts of symbols and emoji in your source code. It’s important to put this tag near the beginning of your code so the browser hasn’t already started parsing too much text when it comes across this line; I think the rule is to put it in the first kilobyte of the document, but I’d say the best practice is to put it right at the top of <head>.

As a side note, it looks like Twitter omits the <head> tag for performance reasons (less code to load), but I still like to make it explicit as it’s a clear home for all metadata, styles, etc.

Line 4: <meta name="viewport" content="width=device-...

Perfect answer: This meta tag in the source code is for properly sizing the webpage on small screens, like smartphones. If you remember the original iPhone keynote, Steve Jobs showed the entire New York Times website on that tiny 4.5-inch screen; back then it was an amazing feature that you had to pinch to zoom to actually be able to read.

Now that websites are responsive by design, width=device-width tells the browser to use 100% of the device’s width as the viewport so there’s no horizontal scrolling, but you can even specify specific pixel values for width. The standard best practice is to set the initial scale to 1 and the width to device-width so people can still zoom around if they wish.

The screenshot of the source code doesn’t show these values but it’s good to know: Twitter also applies user-scalable=0 which, as the name suggests, disables the ability to zoom. This is not good for accessibility but makes the webpage feel more like a native app. It also sets maximum-scale=1 for the same reason (you can use minimum and maximum scale to clamp the zoom-ablity between these values). In general, setting the full width and initial scale is enough.

Line 5: <meta property="og:site_name" content="Twitt...

About 50% of all candidates knew about Open Graph tags, and a good answer to this question shows that they know about SEO.

Perfect answer: This tag is an Open Graph (OG) meta tag for the site name, Twitter. The Open Graph protocol was made by Facebook to make it easier to unfurl links and show their previews in a nice card layout; developers can add all sorts of authorship details and cover images for fancy sharing. In fact, these days it’s even common to auto-generate the open graph image using something like Puppeteer. (CSS-Tricks uses a WordPress plugin that does it.)

Another interesting side note is that meta tags usually have the name attribute, but OG uses the non-standard property attribute. I guess that’s just Facebook being Facebook? The title, URL, and description Open Graph tags are kinda redundant because we already have regular meta tags for these, but people add them just to be safe. Most sites these days use a combination of Open Graph and other metatags and the content on a page to generate rich previews.

Line 6: <meta name="apple-mobile-web-app-title" cont...

Most candidates didn’t know about this one, but experienced developers can talk about how to optimize a website for Apple devices, like apple-touch-icons and Safari pinned tab SVGs.

Perfect answer: You can pin a website on an iPhone’s homescreen to make it feel like a native app. Safari doesn’t support progressive web apps and you can’t really use other browser engines on iOS, so you don’t really have other options if you want that native-like experience, which Twitter, of course, likes. So they add this to tell Safari that the title of this app is Twitter. The next line is similar and controls how the status bar should look like when the app has launched.

Line 8: <meta name="theme-color" content="#ffffff"...

Perfect answer: This is the proper web standards-esque equivalent of the Apple status bar color meta tag. It tells the browser to theme the surrounding UI. Chrome on Android and Brave on desktop both do a pretty good job with that. You can put any CSS color in the content, and can even use the media attribute to only show this color for a specific media query like, for example, to support a dark theme. You can also define this and additional properties in the web app manifest.

Line 9: <meta http-equiv="origin-trial" content="...

Nobody I interviewed knew about this one. I would assume that you’d know this only if you have in-depth knowledge about all the new things that are happening on the standards track.

Perfect answer: Origin trials let us use new and experimental features on our site and the feedback is tracked by the user agent and reported to the web standards community without users having to opt-in to a feature flag. For example, Edge has an origin trial for dual-screen and foldable device primitives, which is pretty cool as you can make interesting layouts based on whether a foldable phone is opened or closed.

Also accepted: I don’t know about this one.

Line 10: html{-ms-text-size-adjust:100%;-webkit-text...

Almost nobody knew about this one too; only if you know about CSS edge cases and optimizations, you’d be able to figure this line out.

Perfect answer: Imagine that you don’t have a mobile responsive site and you open it on a small screen, so the browser might resize the text to make it bigger so it’s easier to read. The CSS text-size-adjust property can either disable this feature with the value none or specify a percentage up to which the browser is allowed to make the text bigger.

In this case, Twitter says the maximum is 100%, so the text should be no bigger than the actual size; they just do that because their site is already responsive and they don’t want to risk a browser breaking the layout with a larger font size. This is applied to the root HTML tag so it applies to everything inside it. Since this is an experimental CSS property, vendor prefixes are required. Also, there’s a missing <style> before this CSS, but I’m guessing that’s minified in the previous line and we don’t see it.

Also accepted: I don’t know about this property in specific but the -ms and -webkit- are vendor prefixes needed by Internet Explorer and WebKit-based browsers, respectively, for non-standard properties. We used to require these prefixes when CSS3 came out, but as properties go from experimental to stable or are adopted to a standards track, these prefixes go away in favor of a standardized property.

Bonus — Line 11: body{margin:0;}

This line from Twitter’s source code is particularly fun because you can follow-up with a question about the difference between resetting and normalizing a webpage. Almost everyone knew a version of the right answer.

Perfect answer: Because different browsers have different default styles (user agent stylesheet), you want to overwrite them by resetting properties so your site looks the same across devices. In this case, Twitter is telling the browser to remove the body tag’s default margin. This is just to reduce browser inconsistencies, but I prefer normalizing the styles instead of resetting them, i.e., applying the same defaults across browsers rather than removing them altogether. People even used to use * { margin: 0 } which is totally overkill and not great for performance, but now it’s common to import something like normalize.css or reset.css (or even something newer) and start from there.

More lines!

I always enjoy playing with the browser Inspector tool to see how sites are made, which is how I came up with this idea. Even though I consider myself sort of an expert on semantic HTML, I learn something new every time I do this exercise.

Since Twitter is mostly a client-side React app, there’s only a few dozen lines in the source code. Even with that, there’s so much to learn! There are a few more interesting lines in the Twitter source code that I leave as an exercise for you, the reader. How many of them could you explain in an interview?

<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Twitter">

…tells browsers that users can add Twitter as a search engine.

<link rel="preload" as="script" crossorigin="anonymous" href="https://abs.twimg.com/responsive-web/client-web/polyfills.cad508b5.js" nonce="MGUyZTIyN2ItMDM1ZC00MzE5LWE2YmMtYTU5NTg2MDU0OTM1" />

…has many interesting attributes that can be discussed, especially nonce.

<link rel="alternate" hreflang="x-default" href="https://twitter.com/" />

…for international landing pages.

:focus:not([data-focusvisible-polyfill]){outline: none;}

…for removing the focus outline when not using keyboard navigation (the CSS :focus-visible selector is polyfilled here).


Explain the First 10 Lines of Twitter’s Source Code to Me originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

Mondrian Art in CSS From 5 Code Artists

Mondrian is famous for paintings with big thick black lines forming a grid, where each cell is white, red, yellow, or blue. This aesthetic pairs well with the notoriously rectangular web, and that hasn’t gone unnoticed over the years with CSS developers. I saw some Mondrian Art in CSS going around the other day and figured I’d go looking for others I’ve seen over the years and round them up.

Vasilis van Gemert:
What if Mondrian used CSS instead of paint?

Many people have tried to recreate a work of art by Mondriaan with CSS. It seems like a nice and simple exercise: rectangles are easy with CSS, and now with grid, it is easy to recreate most of his works. I tried it as well, and it turned out to be a bit more complicated than I thought. And the results are, well, surprising.

Screenshot of a webpage with a large serif font in various sizes reading What if Mondrian Used CSS instead of Paint? above two paragraphs discussing Mondrian Art in CSS.

Jen Simmons Lab:
Mondrian Art in CSS Grid

I love how Jen went the extra mile with the texture. Like most of these examples, CSS grid is used heavily.

Mondrian Art in CSS Grid from Jen Simmons. Includes rough grungy texture across the entire piece.

Jen Schiffer:
var t;: Piet Mondrian

I started with Mondrian not because he is my favorite artist (he is not), or that his work is very recognizeable (it is), but because I thought it would be a fun (yes) and easy start (lol nope) to this project.

Mondrian Art in CSS randomized 12 times in a 4 by 3 grid of boxes. A bright yellow header is above the grid bearing the site title: var t.

Riley Wong:
Make Your Own Mondrian-Style Painting with Code

There is a 12-step tutorial on GitHub.

Adam Fuhrer:
CSS Mondrian

Generative Piet Mondrian style art using CSS grid.

Screenshot of a full page Mondrian art example. There is a refresh button centered at the bottom of the page.

John Broers:
CSS Mondriaan Grid

An example of Mondrian Art in CSS with a "Generate New" option. The example is a square box with plenty of padding around it on the white background page.

Mondrian Art in CSS From 5 Code Artists originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , ,
[Top]

The Many Faces of VS Code in the Browser

VS Code is built from web technologies (HTML, CSS, and JavaScript), but dare I say today it’s mostly used a local app that’s installed on your machine. That’s starting to shift, though, as there has been an absolute explosion of places VS Code is becoming available to use on the web. I’d say it’s kind of a big deal, as VS Code isn’t just some editor; it’s the predominant editor used by web developers. Availability on the web means being able to use it without installing software, which is significant for places, like schools, where managing all that is a pain, and computers, like Chromebooks, where you don’t really install local software at all.

It’s actually kind of confusing all the different places this shows up, so let’s look at the landscape as I see it today.

vscode.dev

It was just a few weeks ago as I write that Microsoft dropped vscode.dev. Chris Dias:

Modern browsers that support the File System Access API (Edge and Chrome today) allow web pages to access the local file system (with your permission). This simple gateway to the local machine quickly opens some interesting scenarios for using VS Code for the Web as a zero-installation local development tool

It’s just Edge and Chrome that have this API right now, but even if you can’t get it, you can still upload files, or perhaps more usefully, open a repo. If it does work, it’s basically… VS Code in the browser. It can open your local folders and it behaves largely just like your local VS Code app does.

I haven’t worked a full day in it or anything, but basic usage seems about the same. There is some very explicit permission-granting you have to do, and keyboard commands are a bit weird as you’re having to fight the browsers keyboard commands. Plus, there is no working terminal.

Other than that it feels about the same. Even stuff like “Find in Project” seems just as fast as local, even on large sites.

GitHub.dev: The whole “Press Period (.) on any GitHub Repo” Thing

You also get VS Code in the browser if you go to github.dev, but it’s not quite wired up the same.

You don’t have the opportunity here to open a local folder. Instead, you can quickly look at a GitHub repo.

But perhaps even more notably, you can make changes, save the files, then use the Source Control panel right there to commit the code or make a pull request.

You’d think vscode.dev and github.dev would merge into one at some point, but who knows.

Oh and hey, thinking of this in reverse, you can open GitHub repos on your locally installed VS Code as well directly (even without cloning it).

There is no terminal or preview in those first two, but there is with GitHub Codespaces.

GitHub Codespaces is also VS Code in the browser, but fancier. For one thing, you’re auth’d into Microsoft-land while using it, meaning it’s running all your VS Code extensions that you run locally. But perhaps a bigger deal is that you get a working terminal. When you spin it up, you see:

Welcome to Codespaces! You are on our default image.

• It includes runtimes and tools for Python, Node.js, Docker, and more. See the full list here: https://aka.ms/ghcs-default-image
• Want to use a custom image instead? Learn more here: https://aka.ms/configure-codespace

🔍 To explore VS Code to its fullest, search using the Command Palette (Cmd/Ctrl + Shift + P or F1).

📝 Edit away, run your app as usual, and we’ll automatically make it available for you to access.

On a typical npm-powered project, you can npm run you scripts and you’ll get a URL running the project as a preview.

This is in the same territory as Gitpod.

Gitpod is a lot like GitHub CodeSpaces in that it’s VS Code in the browser but with a working terminal. That terminal is like a full-on Docker/Linux environment, so you’ve got a lot of power there. It might even be able to mimic your production environment, assuming you’re using all things that Gitpod supports.

It’s also worth noting that Gitpod jacks in “workspaces” that run services. On that demo project above, a MongoDB instance is running on one port, and a preview server is open on another port, which you can see in a mock browser interface there. Being able to preview the project you’re working on is an absolute must and they are handling that elegantly.

Perhaps you’ll remember we used Gitpod in a video we did about DataStax Astra (jumplink) which worked out very nicely.

My (absolute) guess is that Gitpod could be acquired by Microsoft. It seems like Microsoft is headed in this exact direction and getting bought is certainly better than getting steamrolled by the company that makes the core tech that you’re using. You gotta play the “no—this is good! it validates the market! we baked you an awkward cake!” for a while but I can’t imagine it ends well.

This is also a lot like CodeSandbox or Stackblitz.

Straight up, CodeSandbox and Stackblitz also run VS Code in the browser. Or… something that leverages bits and bobs of VS Code at least (a recent episode of Syntax gets into the StackBlitz approach a bit).

You can also install VS Code on your own server.

That’s what Coder’s code-server ia. So, rather than use someone else’s web version of VS Code, you use your own.

You could run VS Code on a local server, but I imagine the big play here is that you run it on live cloud web servers you control. Maybe servers, you know, with your code running on them, so you can use this to literally edit the files on the server. Who needs VIM when you have full-blown VS Code (lolz).

We talked about the school use case, and I imagine this is compelling for that as well, since the school might not even rely on a third-party service, but host it themselves. The iPad/Chromebook use cases are relevant here, too, and perhaps even more so. The docs say “Preserve battery life when you’re on the go; all intensive tasks run on your server,” which I guess means that unlike vscode.dev where tasks like “Find in Project” are (presumably) done on your local machine, they are done by the server (maybe slower, but not slower than a $ 200 laptop?).


There is clearly something in the water with all this. I’m bullish on web-based IDEs. Just look at what’s happening with Figma (kicking ass), which I would argue is one-third due to the product meetings screen designers need with little bloat, one-third due to the simple team and permissions model, and one-third due to the fact that it’s built web-first.


The post The Many Faces of VS Code in the Browser appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

What if… you could use Visual Studio Code as the editor of in-browser Developer Tools?

It’s not uncommon for my front-end workflow to go something like this:

  1. Work on thing.
  2. See that thing in an automatically refreshed browser.
  3. See something wrong with that thing.
  4. Inspect and correct the thing in DevTools.
  5. Apply the correct code in my code editor.
  6. See that correct code automatically refreshed in the browser.

I know, it’s not always great. But I’d bet the lint in my pocket you do something similar, at least every now and then.

That’s why I was drawn to the title of Chris Hellman’s post: “What if… you could use Visual Studio Code as the editor of in-browser Developer Tools?”

The idea is that VS Code can be used as the editor for DevTools and we can do it today by enabling it as an experimental feature, alongside Microsoft Edge. So, no, this is not like a prime-time ready universal thing, but watch Chris as he activates the feature, connects VS Code to DevTools, gives DevTools access to write files, then inspects the page of a local URL.

Now, those changes I make in DevTools can be synced back to VS Code, and I have direct access to open and view specific files from DevTools to see my code in context. Any changes I make in DevTools get reflected back in the VS Code files, and any changes I make in VS Code are updated live in the browser. Brilliant.

I’m not sure if this will become a thing beyond Edge but that sort of cross-over work between platforms is something that really excites me.

Direct Link to ArticlePermalink


The post What if… you could use Visual Studio Code as the editor of in-browser Developer Tools? appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , , , ,
[Top]

Writing Your Own Code Rules

There comes a time on a project when it’s worth investing in tooling to protect the codebase. I’m not sure how to articulate when, but it’s somewhere after the project has proven to be something long-term and rough edges are starting to show, and before things feel like a complete mess. Avoid premature optimization but avoid, uh, postmature optimization.

Some of this tooling is so easy to implement, it is often done right up-front. I think of Prettier here, a code formatter that keeps your code in shape, usually right as you are coding. There are whole suites of tools you can put in that “as-you-are-coding” bucket, like accessibility linting, compatibility linting, security linting, etc. Webhint bundles a bunch of those together and is probably worth a look.

Then there is tooling that protects your code via more code that you have to write. Tests are the big player here, which can even be set up to run as you code. They are about making sure your code does what it is meant to do, and as such, deliver a hell of a lot of value.

Protecting your code with more code that you write is where I wanted to go with this, not with traditional tests, but with custom linting rules. I thought about it as two different posts about custom linting crossed my desk recently:

I was interested as a user of both ESLint and Stylelint in my main codebase. But fair warning, I found the process for writing custom rules in both of those pretty difficult. You gotta know you way around an Abstract Syntax Tree. It’s nothing like if (rules.find.selector.startsWith("old")) throw("Deprecated selector.") or something easy like that.

I found this all related to an interesting question that came my way:

I work on a development team working on an old project, and we want to get of rid many of our oldest and buggiest CSS selectors. For example, one of us might open a HTML file and see an element with a class name of deprecated-selector, our goal is to have our IDE literally mark it as a linting error and say like “This is a deprecated selector, use .ui-fresh__selector instead”.

The first thing I thought of was a custom Stylelint rules that would look for selectors that your team knows to be deprecated and warn you. But unfortunately, Stylelint is for linting CSS and it sounds like the main issue here is HTML. I know html-inspector had a way to write your own rules, but it’s getting a bit long in the tooth so I don’t know if there is success to be found there or not.


The post Writing Your Own Code Rules appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Web Unleashed is Back! (Use Coupon Code “CSS-Tricks” for 50% Off)

(This is a sponsored post.)

Now in its tenth year (!!!), Web Unleashed is one of the top events for web devs. It’s coming up quick: October 20-22, 2021.

The lineup is amazing. You’ll hear from leaders in the industry, including Ethan Marcotte, Rachel Andrew, Harry Roberts, Addy Osmani, Tracy Lee Ire, Aderinokun, Suz Hinton, Shawn Wang, Jen Looper, and many more.

And don’t forget the coupon code CSS-Tricks as 50% off is a massively good deal! (Here’s where you apply it)

So many hot topics will be covered, including responsive design, optimizing images, open source projects, performance metrics, Gatsby, GraphQL, Applied ML, growing your career, and much much more.

But, seriously folks, take it from someone who has gone to this event a bunch of years… it’s really well done, you’re going to learn a lot, and the price right now is unbeatable. The entire conference clocks in at CA $ 179.16, which is CA $ 89.91 with the CSS-Tricks coupon code. That’s… just go.

Direct Link to ArticlePermalink


The post Web Unleashed is Back! (Use Coupon Code “CSS-Tricks” for 50% Off) appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

One Way to Convert Code Indentation

A question:

If you copy a code sample that uses two-space indentation and you want to convert it to four-space indentation, what’s the *fastest* and easiest option?

Matt Stauffer, Twitter

I wrote about doing this in Sublime Text a few years back. It’s not terribly different in VS Code, I don’t think.

But here’s another way: Use CodePen.

Step 1: Copy and paste to CodePen

Say you found some code elsewhere, just copy and paste it in:

Step 2: Adjust code indentation settings

If you already have a CodePen account, you’ve probably already got it set up, so the default is how you like it. But if it’s not yet, adjust it in the Pen Settings area:

Showing the Pen Settings modal open in CodePen over a demo. the settings show code indentation options for spaces and tabs and for indentation width.
The code was 2-spaces as copy/pasted, and now we’re changing that to 4-spaces as a setting.

Step 3: Format the code

You can manually do it here:

You may not have to use that option at all if you save the Pen and have the “Format on Save” option on, as it will automatically happen.

It’ll kick over to that new 4-space preference right quick:


CodePen uses Prettier under the hood to do this. So you could do this anywhere you have access to a working version of Prettier, but it might be easier on CodePen since there’s nothing like editing config or anything to adjust how you want it. Prettier has its own playground as well, which is likely just as easy as CodePen, except that on CodePen you might already have your preferences set up, saving a step.


The post One Way to Convert Code Indentation appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Three Buggy React Code Examples and How to Fix Them

There’s usually more than one way to code a thing in React. And while it’s possible to create the same thing different ways, there may be one or two approaches that technically work “better” than others. I actually run into plenty of examples where the code used to build a React component is technically “correct” but opens up issues that are totally avoidable.

So, let’s look at some of those examples. I’m going to provide three instances of “buggy” React code that technically gets the job done for a particular situation, and ways it can be improved to be more maintainable, resilient, and ultimately functional.

This article assumes some knowledge of React hooks. It isn’t an introduction to hooks—you can find a good introduction from Kingsley Silas on CSS Tricks, or take a look at the React docs to get acquainted with them. We also won’t be looking at any of that exciting new stuff coming up in React 18. Instead, we’re going to look at some subtle problems that won’t completely break your application, but might creep into your codebase and can cause strange or unexpected behavior if you’re not careful.

Buggy code #1: Mutating state and props

It’s a big anti-pattern to mutate state or props in React. Don’t do this!

This is not a revolutionary piece of advice—it’s usually one of the first things you learn if you’re getting started with React. But you might think you can get away with it (because it seems like you can in some cases).

I’m going to show you how bugs might creep into your code if you’re mutating props. Sometimes you’ll want a component that will show a transformed version of some data. Let’s create a parent component that holds a count in state and a button that will increment it. We’ll also make a child component that receives the count via props and shows what the count would look like with 5 added to it.

Here’s a Pen that demonstrates a naïve approach:

This example works. It does what we want it to do: we click the increment button and it adds one to the count. Then the child component is re-rendered to show what the count would look like with 5 added on. We changed the props in the child here and it works fine! Why has everybody been telling us mutating props is so bad?

Well, what if later we refactor the code and need to hold the count in an object? This might happen if we need to store more properties in the same useState hook as our codebase grows larger.

Instead of incrementing the number held in state, we increment the count property of an object held in state. In our child component, we receive the object through props and add to the count property to show what the count would look like if we added 5.

Let’s see how this goes. Try incrementing the state a few times in this pen:

Oh no! Now when we increment the count it seems to add 6 on every click! Why is this happening? The only thing that changed between these two examples is that we used an object instead of a number!

More experienced JavaScript programmers will know that the big difference here is that primitive types such as numbers, booleans and strings are immutable and passed by value, whereas objects are passed by reference.

This means that:

  • If you put a number in a variable, assign another variable to it, then change the second variable, the first variable will not be changed.
  • If you if you put an object in a variable, assign another variable to it, then change the second variable, the first variable will get changed.

When the child component changes a property of the state object, it’s adding 5 to the same object React uses when updating the state. This means that when our increment function fires after a click, React uses the same object after it has been manipulated by our child component, which shows as adding 6 on every click.

The solution

There are multiple ways to avoid these problems. For a situation as simple as this, you could avoid any mutation and express the change in a render function:

function Child({state}){   return <div><p>count + 5 = {state.count + 5} </p></div> }

However, in a more complicated case, you might need to reuse state.count + 5 multiple times or pass the transformed data to multiple children.

One way to do this is to create a copy of the prop in the child, then transform the properties on the cloned data. There’s a couple of different ways to clone objects in JavaScript with various tradeoffs. You can use object literal and spread syntax:

function Child({state}){ const copy = {...state};   return <div><p>count + 5 = {copy.count + 5} </p></div> }

But if there are nested objects, they will still reference the old version. Instead, you could convert the object to JSON then immediately parse it:

JSON.parse(JSON.stringify(myobject)) 

This will work for most simple object types. But if your data uses more exotic types, you might want to use a library. A popular method would be to use lodash’s deepClone. Here’s a Pen that shows a fixed version using object literal and spread syntax to clone the object:

One more option is to use a library like Immutable.js. If you have a rule to only use immutable data structures, you’ll be able to trust that your data won’t get unexpectedly mutated. Here’s one more example using the immutable Map class to represent the state of the counter app:

Buggy code #2: Derived state

Let’s say we have a parent and a child component. They both have useState hooks holding a count. And let’s say the parent passes its state down as prop down to the child, which the child uses to initialize its count.

function Parent(){   const [parentCount,setParentCount] = useState(0);   return <div>     <p>Parent count: {parentCount}</p>     <button onClick={()=>setParentCount(c=>c+1)}>Increment Parent</button>     <Child parentCount={parentCount}/>   </div>; }  function Child({parentCount}){  const [childCount,setChildCount] = useState(parentCount);   return <div>     <p>Child count: {childCount}</p>     <button onClick={()=>setChildCount(c=>c+1)}>Increment Child</button>   </div>; }

What happens to the child’s state when the parent’s state changes, and the child is re-rendered with different props? Will the child state remain the same or will it change to reflect the new count that was passed to it?

We’re dealing with a function, so the child state should get blown away and replaced right? Wrong! The child’s state trumps the new prop from the parent. After the child component’s state is initialized in the first render, it’s completely independent from any props it receives.

React stores component state for each component in the tree and the state only gets blown away when the component is removed. Otherwise, the state won’t be affected by new props.

Using props to initialize state is called “derived state” and it is a bit of an anti-pattern. It removes the benefit of a component having a single source of truth for its data.

Using the key prop

But what if we have a collection of items we want to edit using the same type of child component, and we want the child to hold a draft of the item we’re editing? We’d need to reset the state of the child component each time we switch items from the collection.

Here’s an example: Let’s write an app where we can write a daily list of five thing’s we’re thankful for each day. We’ll use a parent with state initialized as an empty array which we’re going to fill up with five string statements.

Then we’ll have a a child component with a text input to enter our statement.

We’re about to use a criminal level of over-engineering in our tiny app, but it’s to illustrate a pattern you might need in a more complicated project: We’re going to hold the draft state of the text input in the child component.

Lowering the state to the child component can be a performance optimization to prevent the parent re-rendering when the input state changes. Otherwise the parent component will re-render every time there is a change in the text input.

We’ll also pass down an example statement as a default value for each of the five notes we’ll write.

Here’s a buggy way to do this:

// These are going to be our default values for each of the five notes // To give the user an idea of what they might write const ideaList = ["I'm thankful for my friends",                   "I'm thankful for my family",                   "I'm thankful for my health",                   "I'm thankful for my hobbies",                   "I'm thankful for CSS Tricks Articles"]  const maxStatements = 5;  function Parent(){   const [list,setList] = useState([]);      // Handler function for when the statement is completed   // Sets state providing a new array combining the current list and the new item    function onStatementComplete(payload){     setList(list=>[...list,payload]);   }   // Function to reset the list back to an empty array    function reset(){     setList([]);   }   return <div>     <h1>Your thankful list</h1>     <p>A five point list of things you're thankful for:</p>      {/* First we list the statements that have been completed*/}     {list.map((item,index)=>{return <p>Item {index+1}: {item}</p>})}      {/* If the length of the list is under our max statements length, we render      the statement form for the user to enter a new statement.     We grab an example statement from the idealist and pass down the onStatementComplete function.     Note: This implementation won't work as expected*/}     {list.length<maxStatements ?        <StatementForm initialStatement={ideaList[list.length]} onStatementComplete={onStatementComplete}/>       :<button onClick={reset}>Reset</button>     }   </div>; }  // Our child StatementForm component This accepts the example statement for it's initial state and the on complete function function StatementForm({initialStatement,onStatementComplete}){    // We hold the current state of the input, and set the default using initialStatement prop  const [statement,setStatement] = useState(initialStatement);    return <div>     {/*On submit we prevent default and fire the onStatementComplete function received via props*/}     <form onSubmit={(e)=>{e.preventDefault(); onStatementComplete(statement)}}>     <label htmlFor="statement-input">What are you thankful for today?</label><br/>     {/* Our controlled input below*/}     <input id="statement-input" onChange={(e)=>setStatement(e.target.value)} value={statement} type="text"/>     <input type="submit"/>       </form>   </div> }

There’s a problem with this: each time we submit a completed statement, the input incorrectly holds onto the submitted note in the textbox. We want to replace it with an example statement from our list.

Even though we’re passing down a different example string every time, the child remembers the old state and our newer prop is ignored. You could potentially check whether the props have changed on every render in a useEffect, and then reset the state if they have. But that can cause bugs when different parts of your data use the same values and you want to force the child state to reset even though the prop remains the same.

The solution

If you need a child component where the parent needs the ability to reset the child on demand, there is a way to do it: it’s by changing the key prop on the child.

You might have seen this special key prop from when you’re rendering elements based on an array and React throws a warning asking you to provide a key for each element. Changing the key of a child element ensures React creates a brand new version of the element. It’s a way of telling React that you are rendering a conceptually different item using the same component.

Let’s add a key prop to our child component. The value is the index we’re about to fill with our statement:

<StatementForm key={list.length} initialStatement={ideaList[list.length]} onStatementComplte={onStatementComplete}/>

Here’s what this looks like in our list app:

Note the only thing that changed here is that the child component now has a key prop based on the array index we’re about to fill. Yet, the behavior of the component has completely changed.

Now each time we submit and finish writing out statement, the old state in the child component gets thrown away and replaced with the example statement.

Buggy code #3: Stale closure bugs

This is a common issue with React hooks. There’s previously been a CSS-Tricks article about dealing with stale props and states in React’s functional components.

Let’s take a look at a few situations where you might run into trouble. The first crops up is when using useEffect. If we’re doing anything asynchronous inside of useEffect we can get into trouble using old state or props.

Here’s an example. We need to increment a count every second. We set it up on the first render with a useEffect, providing a closure that increments the count as the first argument, and an empty array as the second argument. We’ll give it the empty array as we don’t want React to restart the interval on every render.

function Counter() {    let [count, setCount] = useState(0);    useEffect(() => {     let id = setInterval(() => {       setCount(count + 1);     }, 1000);     return () => clearInterval(id);   },[]);    return <h1>{count}</h1>; }

Oh no! The count gets incremented to 1 but never changes after that! Why is this happening?

It’s to do with two things:

Having a look at the MDN docs on closures, we can see:

A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.

The “lexical environment” in which our useEffect closure is declared is inside our Counter React component. The local variable we’re interested is count, which is zero at the time of the declaration (the first render).

The problem is, this closure is never declared again. If the count is zero at the time declaration, it will always be zero. Each time the interval fires, it’s running a function that starts with a count of zero and increments it to 1.

So how might we get the function declared again? This is where the second argument of the useEffect call comes in. We thought we were extremely clever only starting off the interval once by using the empty array, but in doing so we shot ourselves in the foot. If we had left out this argument, the closure inside useEffect would get declared again with a new count every time.

The way I like to think about it is that the useEffect dependency array does two things:

  • It will fire the useEffect function when the dependency changes.
  • It will also redeclare the closure with the updated dependency, keeping the closure safe from stale state or props.

In fact, there’s even a lint rule to keep your useEffect instances safe from stale state and props by making sure you add the right dependencies to the second argument.

But we don’t actually want to reset our interval every time the component gets rendered either. How do we solve this problem then?

The solution

Again, there are multiple solutions to our problem here. Let’s start with the easiest: not using the count state at all and instead passing a function into our setState call:

function Counter() {    let [count, setCount] = useState(0);    useEffect(() => {     let id = setInterval(() => {       setCount(prevCount => prevCount+ 1);     }, 1000);     return () => clearInterval(id);   },[]);    return <h1>{count}</h1>; }

That was easy. Another option is to use the useRef hook like this to keep a mutable reference of the count:

function Counter() {   let [count, setCount] = useState(0);   const countRef = useRef(count)      function updateCount(newCount){     setCount(newCount);     countRef.current = newCount;   }    useEffect(() => {     let id = setInterval(() => {       updateCount(countRef.current + 1);     }, 1000);     return () => clearInterval(id);   },[]);    return <h1>{count}</h1>; }  ReactDOM.render(<Counter/>,document.getElementById("root"))

To go more in depth on using intervals and hooks you can take a look at this article about creating a useInterval in React by Dan Abramov, who is one of the React core team members. He takes a different route where, instead of holding the count in a ref, he places the entire closure in a ref.

To go more in depth on useEffect you can have a look at his post on useEffect.

More stale closure bugs

But stale closures won’t just appear in useEffect. They can also turn up in event handlers and other closures inside your React components. Let’s have a look at a React component with a stale event handler; we’ll create a scroll progress bar that does the following:

  • increases its width along the screen as the user scrolls
  • starts transparent and becomes more and more opaque as the user scrolls
  • provides the user with a button that randomizes the color of the scroll bar

We’re going to leave the progress bar outside of the React tree and update it in the event handler. Here’s our buggy implementation:

<body> <div id="root"></div> <div id="progress"></div> </body>
function Scroller(){    // We'll hold the scroll position in one state   const [scrollPosition, setScrollPosition] = useState(window.scrollY);   // And the current color in another   const [color,setColor] = useState({r:200,g:100,b:100});      // We assign out scroll listener on the first render   useEffect(()=>{    document.addEventListener("scroll",handleScroll);     return ()=>{document.removeEventListener("scroll",handleScroll);}   },[]);      // A function to generate a random color. To make sure the contrast is strong enough   // each value has a minimum value of 100   function onColorChange(){     setColor({r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155});   }      // This function gets called on the scroll event   function handleScroll(e){     // First we get the value of how far down we've scrolled     const scrollDistance = document.body.scrollTop || document.documentElement.scrollTop;     // Now we grab the height of the entire document     const documentHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;      // And use these two values to figure out how far down the document we are     const percentAlong =  (scrollDistance / documentHeight);     // And use these two values to figure out how far down the document we are     const progress = document.getElementById("progress");     progress.style.width = `$  {percentAlong*100}%`;     // Here's where our bug is. Resetting the color here will mean the color will always      // be using the original state and never get updated     progress.style.backgroundColor = `rgba($  {color.r},$  {color.g},$  {color.b},$  {percentAlong})`;     setScrollPosition(percentAlong);   }      return <div className="scroller" style={{backgroundColor:`rgb($  {color.r},$  {color.g},$  {color.b})`}}>     <button onClick={onColorChange}>Change color</button>     <span class="percent">{Math.round(scrollPosition* 100)}%</span>   </div> }  ReactDOM.render(<Scroller/>,document.getElementById("root"))

Our bar gets wider and increasingly more opaque as the page scrolls. But if you click the change color button, our randomized colors are not affecting the progress bar. We’re getting this bug because the closure is affected by component state, and this closure is never being re-declared so we only get the original value of the state and no updates.

You can see how setting up closures that call external APIs using React state, or component props might give you grief if you’re not careful.

The solution

Again, there are multiple ways to fix this problem. We could keep the color state in a mutable ref which we could later use in our event handler:

const [color,setColor] = useState({r:200,g:100,b:100}); const colorRef = useRef(color);  function onColorChange(){   const newColor = {r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155};   setColor(newColor);   colorRef.current=newColor;   progress.style.backgroundColor = `rgba($  {newColor.r},$  {newColor.g},$  {newColor.b},$  {scrollPosition})`; }

This works well enough but it doesn’t feel ideal. You may need to write code like this if you’re dealing with third-party libraries and you can’t find a way to pull their API into your React tree. But by keeping one of our elements out of the React tree and updating it inside of our event handler, we’re swimming against the tide.

This is a simple fix though, as we’re only dealing with the DOM API. An easy way to refactor this is to include the progress bar in our React tree and render it in JSX allowing it to reference the component’s state. Now we can use the event handling function purely for updating state.

function Scroller(){   const [scrollPosition, setScrollPosition] = useState(window.scrollY);   const [color,setColor] = useState({r:200,g:100,b:100});      useEffect(()=>{    document.addEventListener("scroll",handleScroll);     return ()=>{document.removeEventListener("scroll",handleScroll);}   },[]);      function onColorChange(){     const newColor = {r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155};     setColor(newColor);   }    function handleScroll(e){     const scrollDistance = document.body.scrollTop || document.documentElement.scrollTop;     const documentHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;     const percentAlong =  (scrollDistance / documentHeight);     setScrollPosition(percentAlong);   }   return <>     <div class="progress" id="progress"    style={{backgroundColor:`rgba($  {color.r},$  {color.g},$  {color.b},$  {scrollPosition})`,width: `$  {scrollPosition*100}%`}}></div>     <div className="scroller" style={{backgroundColor:`rgb($  {color.r},$  {color.g},$  {color.b})`}}>     <button onClick={onColorChange}>Change color</button>     <span class="percent">{Math.round(scrollPosition * 100)}%</span>   </div>   </> }

That feels better. Not only have we removed the chance for our event handler to get stale, we’ve also converted our progress bar into a self contained component which takes advantage of the declarative nature of React.

Also, for a scroll indicator like this, you might not even need JavaScript — have take a look at the up-and-coming @scroll-timeline CSS function or an approach using a gradient from Chris’ book on the greatest CSS tricks!

Wrapping up

We’ve had a look at three different ways you can create bugs in your React applications and some ways to fix them. It can be easy to look at counter examples which follow a happy path and don’t show subtleties in the APIs that might cause problems.

If you still find yourself needing to build a stronger mental model of what your React code is doing, here’s a list of resources which can help:


The post Three Buggy React Code Examples and How to Fix Them appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

How to Code a Playable Synth Keyboard

With a little knowledge of music theory, we can use regular HTML, CSS and JavaScript — without any libraries or audio samples — to create a simple digital instrument. Let’s put that into practice and explore one method for creating a digital synth that can be played and hosted on the internet.

Here’s what we’re making:

We’ll use the AudioContext API to create our sounds digitally, without resorting to samples. But first, let’s work on the keyboard’s appearance.

The HTML structure

We’re going to support a standard western keyboard where every letter between A and ; corresponds to a playable natural note (the white keys), while the row above can be used for the sharps and flats (the black keys). This means our keyboard covers just over an octave, starting at C₃ and ending at E₄. (For anyone unfamiliar with musical notation, the subscript numbers indicate the octave.)

One useful thing we can do is store the note value in a custom note attribute so it’s easy to access in our JavaScript. I’ll print the letters of the computer keyboard, to help our users understand what to press.

<ul id="keyboard">   <li note="C" class="white">A</li>   <li note="C#" class="black">W</li>   <li note="D" class="white offset">S</li>   <li note="D#" class="black">E</li>   <li note="E" class="white offset">D</li>   <li note="F" class="white">F</li>   <li note="F#" class="black">T</li>   <li note="G" class="white offset">G</li>   <li note="G#" class="black">Y</li>   <li note="A" class="white offset">H</li>   <li note="A#" class="black">U</li>   <li note="B" class="white offset">J</li>   <li note="C2" class="white">K</li>   <li note="C#2" class="black">O</li>   <li note="D2" class="white offset">L</li>   <li note="D#2" class="black">P</li>   <li note="E2" class="white offset">;</li> </ul>

The CSS styling

We’ll begin our CSS with some boilerplate:

html {   box-sizing: border-box; }  *, *:before, *:after {   box-sizing: inherit;   font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,     Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; }  body {   margin: 0; }

Let’s specify CSS variables for some of the colors we’ll be using. Feel free to change them to whatever you prefer!

:root {   --keyboard: hsl(300, 100%, 16%);   --keyboard-shadow: hsla(19, 50%, 66%, 0.2);   --keyboard-border: hsl(20, 91%, 5%);   --black-10: hsla(0, 0%, 0%, 0.1);   --black-20: hsla(0, 0%, 0%, 0.2);   --black-30: hsla(0, 0%, 0%, 0.3);   --black-50: hsla(0, 0%, 0%, 0.5);   --black-60: hsla(0, 0%, 0%, 0.6);   --white-20: hsla(0, 0%, 100%, 0.2);   --white-50: hsla(0, 0%, 100%, 0.5);   --white-80: hsla(0, 0%, 100%, 0.8); }

In particular, changing the --keyboard and --keyboard-border variables will change the end result dramatically.

For styling the keys and the keyboard — especially in the pressed states — I owe a lot of my inspiration to this CodePen by zastrow. First, we specify the CSS shared by all the keys:

.white, .black {   position: relative;   float: left;   display: flex;   justify-content: center;   align-items: flex-end;   padding: 0.5rem 0;   user-select: none;   cursor: pointer; }

Using a specific border radius on the first and last key helps make the design look more organic. Without rounding, the top left and top right corners of keys look a little unnatural. Here’s a final design, minus any extra rounding on the first and last keys.

Let’s add some CSS to improve this.

#keyboard li:first-child {   border-radius: 5px 0 5px 5px; }  #keyboard li:last-child {   border-radius: 0 5px 5px 5px; }

The difference is subtle but effective:

Next, we apply the stylings that differentiate the white and black keys. Notice that the white keys have a z-index of 1 and the black keys have a z-index of 2:

.white {   height: 12.5rem;   width: 3.5rem;   z-index: 1;   border-left: 1px solid hsl(0, 0%, 73%);   border-bottom: 1px solid hsl(0, 0%, 73%);   border-radius: 0 0 5px 5px;   box-shadow: -1px 0 0 var(--white-80) inset, 0 0 5px hsl(0, 0%, 80%) inset,     0 0 3px var(--black-20);   background: linear-gradient(to bottom, hsl(0, 0%, 93%) 0%, white 100%);   color: var(--black-30); }  .black {   height: 8rem;   width: 2rem;   margin: 0 0 0 -1rem;   z-index: 2;   border: 1px solid black;   border-radius: 0 0 3px 3px;   box-shadow: -1px -1px 2px var(--white-20) inset,     0 -5px 2px 3px var(--black-60) inset, 0 2px 4px var(--black-50);   background: linear-gradient(45deg, hsl(0, 0%, 13%) 0%, hsl(0, 0%, 33%) 100%);   color: var(--white-50); }

When a key is pressed, we’ll use JavaScript to add a class of "pressed" to the relevant li element. For now, we can test this by adding the class directly to our HTML elements.

.white.pressed {   border-top: 1px solid hsl(0, 0%, 47%);   border-left: 1px solid hsl(0, 0%, 60%);   border-bottom: 1px solid hsl(0, 0%, 60%);   box-shadow: 2px 0 3px var(--black-10) inset,     -5px 5px 20px var(--black-20) inset, 0 0 3px var(--black-20);   background: linear-gradient(to bottom, white 0%, hsl(0, 0%, 91%) 100%);   outline: none; }  .black.pressed {   box-shadow: -1px -1px 2px var(--white-20) inset,     0 -2px 2px 3px var(--black-60) inset, 0 1px 2px var(--black-50);   background: linear-gradient(     to right,     hsl(0, 0%, 27%) 0%,     hsl(0, 0%, 13%) 100%   );   outline: none; }

Certain white keys need to be moved toward the left so that they sit under the black keys. We give these a class of "offset" in our HTML, so we can keep the CSS simple:

.offset {   margin: 0 0 0 -1rem; }

If you’ve followed the CSS up to this point, you should have something like this:

Finally, we’ll style the keyboard itself:

#keyboard {   height: 15.25rem;   width: 41rem;   margin: 0.5rem auto;   padding: 3rem 0 0 3rem;   position: relative;   border: 1px solid var(--keyboard-border);   border-radius: 1rem;   background-color: var(--keyboard);   box-shadow: 0 0 50px var(--black-50) inset, 0 1px var(--keyboard-shadow) inset,     0 5px 15px var(--black-50); }

We now have a nice-looking CSS keyboard, but it’s not interactive and it doesn’t make any sounds. To do this, we’ll need JavaScript.

Musical JavaScript

To create the sounds for our synth, we don’t want to rely on audio samples — that’d be cheating! Instead, we can use the web’s AudioContext API, which has tools that can help us turn digital waveforms into sounds.

To create a new audio context, we can use:

const audioContext = new (window.AudioContext || window.webkitAudioContext)();

Before using our audioContext it will be helpful to select all our note elements in the HTML. We can use this helper to easily query the elements:

const getElementByNote = (note) =>   note && document.querySelector(`[note="$ {note}"]`);

We can then store the elements in an object, where the key of the object is the key that a user would press on the keyboard to play that note.

const keys = {   A: { element: getElementByNote("C"), note: "C", octaveOffset: 0 },   W: { element: getElementByNote("C#"), note: "C#", octaveOffset: 0 },   S: { element: getElementByNote("D"), note: "D", octaveOffset: 0 },   E: { element: getElementByNote("D#"), note: "D#", octaveOffset: 0 },   D: { element: getElementByNote("E"), note: "E", octaveOffset: 0 },   F: { element: getElementByNote("F"), note: "F", octaveOffset: 0 },   T: { element: getElementByNote("F#"), note: "F#", octaveOffset: 0 },   G: { element: getElementByNote("G"), note: "G", octaveOffset: 0 },   Y: { element: getElementByNote("G#"), note: "G#", octaveOffset: 0 },   H: { element: getElementByNote("A"), note: "A", octaveOffset: 1 },   U: { element: getElementByNote("A#"), note: "A#", octaveOffset: 1 },   J: { element: getElementByNote("B"), note: "B", octaveOffset: 1 },   K: { element: getElementByNote("C2"), note: "C", octaveOffset: 1 },   O: { element: getElementByNote("C#2"), note: "C#", octaveOffset: 1 },   L: { element: getElementByNote("D2"), note: "D", octaveOffset: 1 },   P: { element: getElementByNote("D#2"), note: "D#", octaveOffset: 1 },   semicolon: { element: getElementByNote("E2"), note: "E", octaveOffset: 1 } };

I found it useful to specify the name of the note here, as well as an octaveOffset, which we’ll need when working out the pitch.

We need to supply a pitch in Hz. The equation used to determine pitch is x * 2^(y / 12) where x is the Hz value of a chosen note — usually A₄, which has a pitch of 440Hz — and y is the number of notes above or below that pitch.

That gives us something like this in code:

const getHz = (note = "A", octave = 4) => {   const A4 = 440;   let N = 0;   switch (note) {     default:     case "A":       N = 0;       break;     case "A#":     case "Bb":       N = 1;       break;     case "B":       N = 2;       break;     case "C":       N = 3;       break;     case "C#":     case "Db":       N = 4;       break;     case "D":       N = 5;       break;     case "D#":     case "Eb":       N = 6;       break;     case "E":       N = 7;       break;     case "F":       N = 8;       break;     case "F#":     case "Gb":       N = 9;       break;     case "G":       N = 10;       break;     case "G#":     case "Ab":       N = 11;       break;   }   N += 12 * (octave - 4);   return A4 * Math.pow(2, N / 12); };

Although we’re only using sharps in the rest of our code, I decided to include flats here as well, so this function could easily be re-used in a different context.

For anyone who’s unsure about musical notation, the notes A# and Bb, for example, describe the exact same pitch. We might choose one over another if we’re playing in a particular key, but for our purposes, the difference doesn’t matter.

Playing notes

We’re ready to start playing some notes!

First, we need some way of telling which notes are playing at any given time. Let’s use a Map to do this, as its unique key constraint can help prevent us from triggering the same note multiple times in a single press. Plus, a user can only click one key at a time, so we can store that as a string.

const pressedNotes = new Map(); let clickedKey = "";

We need two functions, one to play a key — which we’ll trigger on keydown or mousedown — and another to stop playing the key — which we’ll trigger on keyup or mouseup.

Each key will be played on its own oscillator with its own gain node (used to control the volume) and its own waveform type (used to determine the timbre of the sound). I’m opting for a "triangle" waveform, but you can use whatever you prefer of "sine", "triangle", "sawtooth" and "square". The spec offers a little more information on these values.

const playKey = (key) => {   if (!keys[key]) {     return;   }    const osc = audioContext.createOscillator();   const noteGainNode = audioContext.createGain();   noteGainNode.connect(audioContext.destination);   noteGainNode.gain.value = 0.5;   osc.connect(noteGainNode);   osc.type = "triangle";    const freq = getHz(keys[key].note, (keys[key].octaveOffset || 0) + 4);    if (Number.isFinite(freq)) {     osc.frequency.value = freq;   }    keys[key].element.classList.add("pressed");   pressedNotes.set(key, osc);   pressedNotes.get(key).start(); };

Our sound could do with some refinement. At the moment, is has a slightly piercing, microwave-buzzer quality to it! But this is enough to get started. We’ll come back and make some tweaks at the end!

Stopping a key is a simpler task. We need to let each note “ring out” for an amount of time after the user lifts their finger (two seconds is about right), as well as make the necessary visual change.

const stopKey = (key) => {   if (!keys[key]) {     return;   }      keys[key].element.classList.remove("pressed");   const osc = pressedNotes.get(key);    if (osc) {     setTimeout(() => {       osc.stop();     }, 2000);      pressedNotes.delete(key);   } };

All that’s left is to add our event listeners:

document.addEventListener("keydown", (e) => {   const eventKey = e.key.toUpperCase();   const key = eventKey === ";" ? "semicolon" : eventKey;      if (!key || pressedNotes.get(key)) {     return;   }   playKey(key); });  document.addEventListener("keyup", (e) => {   const eventKey = e.key.toUpperCase();   const key = eventKey === ";" ? "semicolon" : eventKey;      if (!key) {     return;   }   stopKey(key); });  for (const [key, { element }] of Object.entries(keys)) {   element.addEventListener("mousedown", () => {     playKey(key);     clickedKey = key;   }); }  document.addEventListener("mouseup", () => {   stopKey(clickedKey); });

Note that, while most of our event listeners are added to the HTML document, we can use our keys object to add click listeners to the specific elements we have already queried. We also need to give some special treatment to our highest note, making sure we convert the ";" key into the spelled-out "semicolon" used in our keys object.

We can now play the keys on our synth! There’s just one problem. The sound is still pretty shrill! We might want to knock down the octave of the keyboard by changing the expression that we assign to the freq constant:

const freq = getHz(keys[key].note, (keys[key].octaveOffset || 0) + 3);

You might also be able to hear a “click” at the beginning and end of the sound. We can solve this by quickly fading in and more gradually fading out of each sound.

In music production, we use the term attack to describe how quickly a sound goes from nothing to its maximum volume, and “release” to describe how long it takes for a sound to fade to nothing once it’s no longer played. Another useful concept is decay, the the time taken for sound to go from its peak volume to its sustained volume. Thankfully, our noteGainNode has a gain property with a method called exponentialRampToValueAtTime, which we can use to control attack, release and decay. If we replace our previous playKey function with the following one, we’ll get a much nicer plucky sound:

const playKey = (key) => {   if (!keys[key]) {     return;   }    const osc = audioContext.createOscillator();   const noteGainNode = audioContext.createGain();   noteGainNode.connect(audioContext.destination);    const zeroGain = 0.00001;   const maxGain = 0.5;   const sustainedGain = 0.001;    noteGainNode.gain.value = zeroGain;    const setAttack = () =>     noteGainNode.gain.exponentialRampToValueAtTime(       maxGain,       audioContext.currentTime + 0.01     );   const setDecay = () =>     noteGainNode.gain.exponentialRampToValueAtTime(       sustainedGain,       audioContext.currentTime + 1     );   const setRelease = () =>     noteGainNode.gain.exponentialRampToValueAtTime(       zeroGain,       audioContext.currentTime + 2     );    setAttack();   setDecay();   setRelease();    osc.connect(noteGainNode);   osc.type = "triangle";    const freq = getHz(keys[key].note, (keys[key].octaveOffset || 0) - 1);    if (Number.isFinite(freq)) {     osc.frequency.value = freq;   }    keys[key].element.classList.add("pressed");   pressedNotes.set(key, osc);   pressedNotes.get(key).start(); };

We should have a working, web-ready synth at this point!

The numbers inside our setAttack, setDecay and setRelease functions may seem a bit random, but really they’re just stylistic choices. Try changing them and seeing what happens to the sound. You may end up with something you prefer!

If you’re interested in taking the project further, there are lots of ways you could improve it. Perhaps a volume control, a way to switch between octaves, or a way to choose between waveforms? We could add reverb or a low pass filter. Or perhaps each sound could be made up of multiple oscillators?

For anyone interested in understanding more about how to implement music theory concepts on the web, I recommend looking at the source code of the tonal npm package.


The post How to Code a Playable Synth Keyboard appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]