from Java, SQL and jOOQ. https://ift.tt/RYKXxQp
via IFTTT
I spend a lot of time in DevTools, and I’m sure you do too. Sometimes I even bounce between them, especially when I’m debugging cross-browser issues. DevTools is a lot like browsers themselves — not all of the features in one browser’s DevTools will be the same or supported in another browser’s DevTools.
But there are quite a few DevTools features that are interoperable, even some lesser-known ones that I’m about to share with you.
For the sake of brevity, I use “Chromium” to refer to all Chromium-based browsers, like Chrome, Edge, and Opera, in the article. Many of the DevTools in them offer the exact same features and capabilities as one another, so this is just my shorthand for referring to all of them at once.
Sometimes the DOM tree is full of nodes nested in nodes that are nested in other nodes, and so on. That makes it pretty tough to find the exact one you’re looking for, but you can quickly search the DOM tree using Cmd
+ F
(macOS) or Ctrl
+ F
(Windows).
Additionally, you can also search using a valid CSS selector, like .red
, or using an XPath, like //div/h1
.
In Chromium browsers, the focus automatically jumps to the node that matches the search criteria as you type, which could be annoying if you are working with longer search queries or a large DOM tree. Fortunately, you can disable this behavior by heading to Settings (F1
) → Preferences → Global → Search as you type → Disable.
After you have located the node in the DOM tree, you can scroll the page to bring the node within the viewport by right-clicking on the nod, and selecting “Scroll into view”.
DevTools provides many different ways to access a DOM node directly from the console.
For example, you can use $0
to access the currently selected node in the DOM tree. Chromium browsers take this one step further by allowing you to access nodes selected in the reverse chronological order of historic selection using, $1
, $2
, $3
, etc.
Another thing that Chromium browsers allow you to do is copy the node path as a JavaScript expression in the form of document.querySelector
by right-clicking on the node, and selecting Copy → Copy JS path, which can then be used to access the node in the console.
Here’s another way to access a DOM node directly from the console: as a temporary variable. This option is available by right-clicking on the node and selecting an option. That option is labeled differently in each browser’s DevTools:
DevTools can help visualize elements that match certain properties by displaying a badge next to the node. Badges are clickable, and different browsers offer a variety of different badges.
In Safari, there is a badge button in the Elements panel toolbar which can be used to toggle the visibility of specific badges. For example, if a node has a display: grid
or display: inline-grid
CSS declaration applied to it, a grid
badge is displayed next to it. Clicking on the badge will highlight grid areas, track sizes, line numbers, and more, on the page.
The badges that are currently supported in Firefox’s DevTools are listed in the Firefox source docs. For example, a scroll
badge indicates a scrollable element. Clicking on the badge highlights the element causing the overflow with an overflow
badge next to it.
In Chromium browsers, you can right-click on any node and select “Badge settings…” to open a container that lists all of the available badges. For example, elements with scroll-snap-type
will have a scroll-snap
badge next to it, which on click, will toggle the scroll-snap
overlay on that element.
We’ve been able to take screenshots from some DevTools for a while now, but it’s now available in all of them and includes new ways to take full-page shots.
The process starts by right-clicking on the DOM node you want to capture. Then select the option to capture the node, which is labeled differently depending on which DevTools you’re using.
Repeat the same steps on the html
node to take a full-page screenshot. When you do, though, it’s worth noting that Safari retains the transparency of the element’s background color — Chromium and Firefox will capture it as a white background.
There’s another option! You can take a “responsive” screenshot of the page, which allows you to capture the page at a specific viewport width. As you might expect, each browser has different ways to get there.
Cmd
+ Shift
+ M
(macOS) or Ctrl
+ Shift
+ M
(Windows). Or click the “Devices” icon next to the “Inspect” icon.Chrome lets you visualize and inspect top-layer elements, like a dialog, alert, or modal. When an element is added to the #top-layer
, it gets a top-layer
badge next to it, which on click, jumps you to the top-layer container located just after the </html>
tag.
The order of the elements in the top-layer
container follows the stacking order, which means the last one is on the top. Click the reveal
badge to jump back to the node.
Firefox links the element referencing the ID attribute to its target element in the same DOM and highlights it with an underline. Use CMD
+ Click
(macOS) or CTRL
+ Click
(Windows) )to jump to the target element with the identifier.
Quite a few things, right? It’s awesome that there are some incredibly useful DevTools features that are supported in Chromium, Firefox, and Safari alike. Are there any other lesser-known features supported by all three that you like?
There are a few resources I keep close by to stay on top of what’s new. I thought I’d share them with here:
Some Cross-Browser DevTools Features You Might Not Know originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Doing a quick search here on CSS-Tricks shows just how many different ways there are to approach calendars. Some show how CSS Grid can create the layout efficiently. Some attempt to bring actual data into the mix. Some rely on a framework to help with state management.
There are many considerations when building a calendar component — far more than what is covered in the articles I linked up. If you think about it, calendars are fraught with nuance, from handling timezones and date formats to localization and even making sure dates flow from one month to the next… and that’s before we even get into accessibility and additional layout considerations depending on where the calendar is displayed and whatnot.
Many developers fear the Date()
object and stick with older libraries like moment.js
. But while there are many “gotchas” when it comes to dates and formatting, JavaScript has a lot of cool APIs and stuff to help out!
I don’t want to re-create the wheel here, but I will show you how we can get a dang good calendar with vanilla JavaScript. We’ll look into accessibility, using semantic markup and screenreader-friendly <time>
-tags — as well as internationalization and formatting, using the Intl.Locale
, Intl.DateTimeFormat
and Intl.NumberFormat
-APIs.
In other words, we’re making a calendar… only without the extra dependencies you might typically see used in a tutorial like this, and with some of the nuances you might not typically see. And, in the process, I hope you’ll gain a new appreciation for newer things that JavaScript can do while getting an idea of the sorts of things that cross my mind when I’m putting something like this together.
What should we call our calendar component? In my native language, it would be called “kalender element”, so let’s use that and shorten that to “Kal-El” — also known as Superman’s name on the planet Krypton.
Let’s create a function to get things going:
function kalEl(settings = {}) { ... }
This method will render a single month. Later we’ll call this method from [...Array(12).keys()]
to render an entire year.
One of the common things a typical online calendar does is highlight the current date. So let’s create a reference for that:
const today = new Date();
Next, we’ll create a “configuration object” that we’ll merge with the optional settings
object of the primary method:
const config = Object.assign(
{
locale: (document.documentElement.getAttribute('lang') || 'en-US'),
today: {
day: today.getDate(),
month: today.getMonth(),
year: today.getFullYear()
}
}, settings
);
We check, if the root element (<html>
) contains a lang
-attribute with locale info; otherwise, we’ll fallback to using en-US
. This is the first step toward internationalizing the calendar.
We also need to determine which month to initially display when the calendar is rendered. That’s why we extended the config
object with the primary date
. This way, if no date is provided in the settings
object, we’ll use the today
reference instead:
const date = config.date ? new Date(config.date) : today;
We need a little more info to properly format the calendar based on locale. For example, we might not know whether the first day of the week is Sunday or Monday, depending on the locale. If we have the info, great! But if not, we’ll update it using the Intl.Locale
API. The API has a weekInfo
object that returns a firstDay
property that gives us exactly what we’re looking for without any hassle. We can also get which days of the week are assigned to the weekend
:
if (!config.info) config.info = new Intl.Locale(config.locale).weekInfo || {
firstDay: 7,
weekend: [6, 7]
};
Again, we create fallbacks. The “first day” of the week for en-US
is Sunday, so it defaults to a value of 7
. This is a little confusing, as the getDay
method in JavaScript returns the days as [0-6]
, where 0
is Sunday… don’t ask me why. The weekends are Saturday and Sunday, hence [6, 7]
.
Before we had the Intl.Locale
API and its weekInfo
method, it was pretty hard to create an international calendar without many **objects and arrays with information about each locale or region. Nowadays, it’s easy-peasy. If we pass in en-GB
, the method returns:
// en-GB
{
firstDay: 1,
weekend: [6, 7],
minimalDays: 4
}
In a country like Brunei (ms-BN
), the weekend is Friday and Sunday:
// ms-BN
{
firstDay: 7,
weekend: [5, 7],
minimalDays: 1
}
You might wonder what that minimalDays
property is. That’s the fewest days required in the first week of a month to be counted as a full week. In some regions, it might be just one day. For others, it might be a full seven days.
Next, we’ll create a render
method within our kalEl
-method:
const render = (date, locale) => { ... }
We still need some more data to work with before we render anything:
const month = date.getMonth();
const year = date.getFullYear();
const numOfDays = new Date(year, month + 1, 0).getDate();
const renderToday = (year === config.today.year) && (month === config.today.month);
The last one is a Boolean
that checks whether today
exists in the month we’re about to render.
We’re going to get deeper in rendering in just a moment. But first, I want to make sure that the details we set up have semantic HTML tags associated with them. Setting that up right out of the box gives us accessibility benefits from the start.
First, we have the non-semantic wrapper: <kal-el>
. That’s fine because there isn’t a semantic <calendar>
tag or anything like that. If we weren’t making a custom element, <article>
might be the most appropriate element since the calendar could stand on its own page.
The <time>
element is going to be a big one for us because it helps translate dates into a format that screenreaders and search engines can parse more accurately and consistently. For example, here’s how we can convey “January 2023” in our markup:
<time datetime="2023-01">January <i>2023</i></time>
The row above the calendar’s dates containing the names of the days of the week can be tricky. It’s ideal if we can write out the full names for each day — e.g. Sunday, Monday, Tuesday, etc. — but that can take up a lot of space. So, let’s abbreviate the names for now inside of an <ol>
where each day is a <li>
:
<ol>
<li><abbr title="Sunday">Sun</abbr></li>
<li><abbr title="Monday">Mon</abbr></li>
<!-- etc. -->
</ol>
We could get tricky with CSS to get the best of both worlds. For example, if we modified the markup a bit like this:
<ol>
<li>
<abbr title="S">Sunday</abbr>
</li>
</ol>
…we get the full names by default. We can then “hide” the full name when space runs out and display the title
attribute instead:
@media all and (max-width: 800px) {
li abbr::after {
content: attr(title);
}
}
But, we’re not going that way because the Intl.DateTimeFormat
API can help here as well. We’ll get to that in the next section when we cover rendering.
Each date in the calendar grid gets a number. Each number is a list item (<li>
) in an ordered list (<ol>
), and the inline <time>
tag wraps the actual number.
<li>
<time datetime="2023-01-01">1</time>
</li>
And while I’m not planning to do any styling just yet, I know I will want some way to style the date numbers. That’s possible as-is, but I also want to be able to style weekday numbers differently than weekend numbers if I need to. So, I’m going to include data-*
attributes specifically for that: data-weekend
and data-today
.
There are 52 weeks in a year, sometimes 53. While it’s not super common, it can be nice to display the number for a given week in the calendar for additional context. I like having it now, even if I don’t wind up not using it. But we’ll totally use it in this tutorial.
We’ll use a data-weeknumber
attribute as a styling hook and include it in the markup for each date that is the week’s first date.
<li data-day="7" data-weeknumber="1" data-weekend="">
<time datetime="2023-01-08">8</time>
</li>
Let’s get the calendar on a page! We already know that <kal-el>
is the name of our custom element. First thing we need to configure it is to set the firstDay
property on it, so the calendar knows whether Sunday or some other day is the first day of the week.
<kal-el data-firstday="${ config.info.firstDay }">
We’ll be using template literals to render the markup. To format the dates for an international audience, we’ll use the Intl.DateTimeFormat
API, again using the locale
we specified earlier.
When we call the month
, we can set whether we want to use the long
name (e.g. February) or the short
name (e.g. Feb.). Let’s use the long
name since it’s the title above the calendar:
<time datetime="${year}-${(pad(month))}">
${new Intl.DateTimeFormat(
locale,
{ month:'long'}).format(date)} <i>${year}</i>
</time>
For weekdays displayed above the grid of dates, we need both the long
(e.g. “Sunday”) and short
(abbreviated, ie. “Sun”) names. This way, we can use the “short” name when the calendar is short on space:
Intl.DateTimeFormat([locale], { weekday: 'long' })
Intl.DateTimeFormat([locale], { weekday: 'short' })
Let’s make a small helper method that makes it a little easier to call each one:
const weekdays = (firstDay, locale) => {
const date = new Date(0);
const arr = [...Array(7).keys()].map(i => {
date.setDate(5 + i)
return {
long: new Intl.DateTimeFormat([locale], { weekday: 'long'}).format(date),
short: new Intl.DateTimeFormat([locale], { weekday: 'short'}).format(date)
}
})
for (let i = 0; i < 8 - firstDay; i++) arr.splice(0, 0, arr.pop());
return arr;
}
Here’s how we invoke that in the template:
<ol>
${weekdays(config.info.firstDay,locale).map(name => `
<li>
<abbr title="${name.long}">${name.short}</abbr>
</li>`).join('')
}
</ol>
And finally, the days, wrapped in an <ol>
element:
${[...Array(numOfDays).keys()].map(i => {
const cur = new Date(year, month, i + 1);
let day = cur.getDay(); if (day === 0) day = 7;
const today = renderToday && (config.today.day === i + 1) ? ' data-today':'';
return `
<li data-day="${day}"${today}${i === 0 || day === config.info.firstDay ? ` data-weeknumber="${new Intl.NumberFormat(locale).format(getWeek(cur))}"`:''}${config.info.weekend.includes(day) ? ` data-weekend`:''}>
<time datetime="${year}-${(pad(month))}-${pad(i)}" tabindex="0">
${new Intl.NumberFormat(locale).format(i + 1)}
</time>
</li>`
}).join('')}
Let’s break that down:
day
variable for the current day in the iteration.Intl.Locale
API and getDay()
.day
is equal to today
, we add a data-*
attribute.<li>
element as a string with merged data.tabindex="0"
makes the element focusable, when using keyboard navigation, after any positive tabindex values (Note: you should never add positive tabindex-values)To “pad” the numbers in the datetime
attribute, we use a little helper method:
const pad = (val) => (val + 1).toString().padStart(2, '0');
Again, the “week number” is where a week falls in a 52-week calendar. We use a little helper method for that as well:
function getWeek(cur) {
const date = new Date(cur.getTime());
date.setHours(0, 0, 0, 0);
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
const week = new Date(date.getFullYear(), 0, 4);
return 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + (week.getDay() + 6) % 7) / 7);
}
I didn’t write this getWeek
-method. It’s a cleaned up version of this script.
And that’s it! Thanks to the Intl.Locale
, Intl.DateTimeFormat
and Intl.NumberFormat
APIs, we can now simply change the lang
-attribute of the <html>
element to change the context of the calendar based on the current region:
You might recall how all the days are just one <ol>
with list items. To style these into a readable calendar, we dive into the wonderful world of CSS Grid. In fact, we can repurpose the same grid from a starter calendar template right here on CSS-Tricks, but updated a smidge with the :is()
relational pseudo to optimize the code.
Notice that I’m defining configurable CSS variables along the way (and prefixing them with ---kalel-
to avoid conflicts).
kal-el :is(ol, ul) {
display: grid;
font-size: var(--kalel-fz, small);
grid-row-gap: var(--kalel-row-gap, .33em);
grid-template-columns: var(--kalel-gtc, repeat(7, 1fr));
list-style: none;
margin: unset;
padding: unset;
position: relative;
}
Let’s draw borders around the date numbers to help separate them visually:
kal-el :is(ol, ul) li {
border-color: var(--kalel-li-bdc, hsl(0, 0%, 80%));
border-style: var(--kalel-li-bds, solid);
border-width: var(--kalel-li-bdw, 0 0 1px 0);
grid-column: var(--kalel-li-gc, initial);
text-align: var(--kalel-li-tal, end);
}
The seven-column grid works fine when the first day of the month is also the first day of the week for the selected locale). But that’s the exception rather than the rule. Most times, we’ll need to shift the first day of the month to a different weekday.
Remember all the extra data-*
attributes we defined when writing our markup? We can hook into those to update which grid column (--kalel-li-gc
) the first date number of the month is placed on:
[data-firstday="1"] [data-day="3"]:first-child {
--kalel-li-gc: 1 / 4;
}
In this case, we’re spanning from the first grid column to the fourth grid column — which will automatically “push” the next item (Day 2) to the fifth grid column, and so forth.
Let’s add a little style to the “current” date, so it stands out. These are just my styles. You can totally do what you’d like here.
[data-today] {
--kalel-day-bdrs: 50%;
--kalel-day-bg: hsl(0, 86%, 40%);
--kalel-day-hover-bgc: hsl(0, 86%, 70%);
--kalel-day-c: #fff;
}
I like the idea of styling the date numbers for weekends differently than weekdays. I’m going to use a reddish color to style those. Note that we can reach for the :not()
pseudo-class to select them while leaving the current date alone:
[data-weekend]:not([data-today]) {
--kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
}
Oh, and let’s not forget the week numbers that go before the first date number of each week. We used a data-weeknumber
attribute in the markup for that, but the numbers won’t actually display unless we reveal them with CSS, which we can do on the ::before
pseudo-element:
[data-weeknumber]::before {
display: var(--kalel-weeknumber-d, inline-block);
content: attr(data-weeknumber);
position: absolute;
inset-inline-start: 0;
/* additional styles */
}
We’re technically done at this point! We can render a calendar grid that shows the dates for the current month, complete with considerations for localizing the data by locale, and ensuring that the calendar uses proper semantics. And all we used was vanilla JavaScript and CSS!
But let’s take this one more step…
Maybe you need to display a full year of dates! So, rather than render the current month, you might want to display all of the month grids for the current year.
Well, the nice thing about the approach we’re using is that we can call the render
method as many times as we want and merely change the integer that identifies the month on each instance. Let’s call it 12 times based on the current year.
as simple as calling the render
-method 12 times, and just change the integer for month
— i
:
[...Array(12).keys()].map(i =>
render(
new Date(date.getFullYear(),
i,
date.getDate()),
config.locale,
date.getMonth()
)
).join('')
It’s probably a good idea to create a new parent wrapper for the rendered year. Each calendar grid is a <kal-el>
element. Let’s call the new parent wrapper <jor-el>
, where Jor-El is the name of Kal-El’s father.
<jor-el id="app" data-year="true">
<kal-el data-firstday="7">
<!-- etc. -->
</kal-el>
<!-- other months -->
</jor-el>
We can use <jor-el>
to create a grid for our grids. So meta!
jor-el {
background: var(--jorel-bg, none);
display: var(--jorel-d, grid);
gap: var(--jorel-gap, 2.5rem);
grid-template-columns: var(--jorel-gtc, repeat(auto-fill, minmax(320px, 1fr)));
padding: var(--jorel-p, 0);
}
I read an excellent book called Making and Breaking the Grid the other day and stumbled on this beautiful “New Year’s poster”:
I figured we could do something similar without changing anything in the HTML or JavaScript. I’ve taken the liberty to include full names for months, and numbers instead of day names, to make it more readable. Enjoy!
Making Calendars With Accessibility and Internationalization in Mind originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
You know what it’s like to pick up a new language or framework. Sometimes there’s great documentation to help you find your way through it. But even the best documentation doesn’t cover absolutely everything. And when you work with something that’s new, you’re bound to find a problem that doesn’t have a written solution.
That’s how it was for me the first time I created a React project — and React is one of those frameworks with remarkable documentation, especially now with the beta docs. But I still struggled my way through. It’s been quite a while since that project, but the lessons I gained from it are still fresh in my mind. And even though there are a lot of React “how-to” tutorials in out there, I thought I’d share what I wish I knew when I first used it.
So, that’s what this article is — a list of the early mistakes I made. I hope they help make learning React a lot smoother for you.
TL;DR Use Vite or Parcel.
Create React App (CRA) is a tool that helps you set up a new React project. It creates a development environment with the best configuration options for most React projects. This means you don’t have to spend time configuring anything yourself.
As a beginner, this seemed like a great way to start my work! No configuration! Just start coding!
CRA uses two popular packages to achieve this, webpack and Babel. webpack is a web bundler that optimizes all of the assets in your project, such as JavaScript, CSS, and images. Babel is a tool that allows you to use newer JavaScript features, even if some browsers don’t support them.
Both are good, but there are newer tools that can do the job better, specifically Vite and Speedy Web Compiler (SWC).
These new and improved alternatives are faster and easier to configure than webpack and Babel. This makes it easier to adjust the configuration which is difficult to do in create-react-app without ejecting.
To use them both when setting up a new React project you have to make sure you have Node version 12 or higher installed, then run the following command.
npm create vite
You’ll be asked to pick a name for your project. Once you do that, select React from the list of frameworks. After that, you can select either Javascript + SWC
or Typescript + SWC
Then you’ll have to change directory cd
into your project and run the following command;
npm i && npm run dev
This should run a development server for your site with the URL localhost:5173
And it’s as simple as that.
defaultProps
for default valuesTL;DR Use default function parameters instead.
Data can be passed to React components through something called props
. These are added to a component just like attributes in an HTML element and can be used in a component’s definition by taking the relevant values from the prop object passed in as an argument.
// App.jsx
export default function App() {
return <Card title="Hello" description="world" />
}
// Card.jsx
function Card(props) {
return (
<div>
<h1>{props.title}</h1>
<p>{props.description}</p>
</div>
);
}
export default Card;
If a default value is ever required for a prop
, the defaultProp
property can be used:
// Card.jsx
function Card(props) {
// ...
}
Card.defaultProps = {
title: 'Default title',
description: 'Desc',
};
export default Card;
With modern JavaScript, it is possible to destructure the props
object and assign a default value to it all in the function argument.
// Card.jsx
function Card({title = "Default title", description= "Desc"}) {
return (
<div>
<h1>{title}</h1>
<p>{description}</p>
</div>
)
}
export default Card;
This is more favorable as the code that can be read by modern browsers without the need for extra transformation.
Unfortunately, defaultProps
do require some transformation to be read by the browser since JSX (JavaScript XML) isn’t supported out of the box. This could potentially affect the performance of an application that is using a lot of defaultProps
.
propTypes
TL;DR Use TypeScript.
In React, the propTypes
property can be used to check if a component is being passed the correct data type for its props. They allow you to specify the type of data that should be used for each prop such as a string, number, object, etc. They also allow you to specify if a prop is required or not.
This way, if a component is passed the wrong data type or if a required prop is not being provided, then React will throw an error.
// Card.jsx
import { PropTypes } from "prop-types";
function Card(props) {
// ...
}
Card.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
};
export default Card;
TypeScript provides a level of type safety in data that’s being passed to components. So, sure, propTypes
were a good idea back when I was starting. However, now that TypeScript has become the go-to solution for type safety, I would highly recommend using it over anything else.
// Card.tsx
interface CardProps {
title: string,
description?: string,
}
export default function Card(props: CardProps) {
// ...
}
TypeScript is a programming language that builds on top of JavaScript by adding static type-checking. TypeScript provides a more powerful type system, that can catch more potential bugs and improves the development experience.
TL;DR: Write components as functions
Class components in React are created using JavaScript classes. They have a more object-oriented structure and as well as a few additional features, like the ability to use the this
keyword and lifecycle methods.
// Card.jsx
class Card extends React.Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
<p>{this.props.description}</p>
</div>
)
}
}
export default Card;
I prefer writing components with classes over functions, but JavaScript classes are more difficult for beginners to understand and this
can get very confusing. Instead, I’d recommend writing components as functions:
// Card.jsx
function Card(props) {
return (
<div>
<h1>{props.title}</h1>
<p>{props.description}</p>
</div>
)
}
export default Card;
Function components are simply JavaScript functions that return JSX. They are much easier to read, and do not have additional features like the this
keyword and lifecycle methods which make them more performant than class components.
Function components also have the advantage of using hooks. React Hooks allow you to use state and other React features without writing a class component, making your code more readable, maintainable and reusable.
TL;DR: There’s no need to do it, unless you need hooks.
Since React 17 was released in 2020, it’s now unnecessary to import React at the top of your file whenever you create a component.
import React from 'react'; // Not needed!
export default function Card() {}
But we had to do that before React 17 because the JSX transformer (the thing that converts JSX into regular JavaScript) used a method called React.createElement
that would only work when importing React. Since then, a new transformer has been release which can transform JSX without the createElement
method.
You will still need to import React to use hooks, fragments, and any other functions or components you might need from the library:
import { useState } from 'react';
export default function Card() {
const [count, setCount] = useState(0);
// ...
}
Maybe “mistake” is too harsh a word since some of the better practices came about later. Still, I see plenty of instances where the “old” way of doing something is still being actively used in projects and other tutorials.
To be honest, I probably made way more than five mistakes when getting started. Anytime you reach for a new tool it is going to be more like a learning journey to use it effectively, rather than flipping a switch. But these are the things I still carry with me years later!
If you’ve been using React for a while, what are some of the things you wish you knew before you started? It would be great to get a collection going to help others avoid the same struggles.
5 Mistakes I Made When Starting My First React Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
CSS trigonometry functions are here! Well, they are if you’re using the latest versions of Firefox and Safari, that is. Having this sort of mathematical power in CSS opens up a whole bunch of possibilities. In this tutorial, I thought we’d dip our toes in the water to get a feel for a couple of the newer functions: sin()
and cos()
.
There are other trigonometry functions in the pipeline — including tan()
— so why focus just on sin()
and cos()
? They happen to be perfect for the idea I have in mind, which is to place text along the edge of a circle. That’s been covered here on CSS-Tricks when Chris shared an approach that uses a Sass mixin. That was six years ago, so let’s give it the bleeding edge treatment.
Here’s what I have in mind. Again, it’s only supported in Firefox and Safari at the moment:
So, it’s not exactly like words forming a circular shape, but we are placing text characters along the circle to form a clock face. Here’s some markup we can use to kick things off:
<div class="clock">
<div class="clock-face">
<time datetime="12:00">12</time>
<time datetime="1:00">1</time>
<time datetime="2:00">2</time>
<time datetime="3:00">3</time>
<time datetime="4:00">4</time>
<time datetime="5:00">5</time>
<time datetime="6:00">6</time>
<time datetime="7:00">7</time>
<time datetime="8:00">8</time>
<time datetime="9:00">9</time>
<time datetime="10:00">10</time>
<time datetime="11:00">11</time>
</div>
</div>
Next, here are some super basic styles for the .clock-face
container. I decided to use the <time>
tag with a datetime
attribute.
.clock {
--_ow: clamp(5rem, 60vw, 40rem);
--_w: 88cqi;
aspect-ratio: 1;
background-color: tomato;
border-radius: 50%;
container-type: inline;
display: grid;
height: var(--_ow);
place-content: center;
position: relative;
width var(--_ow);
}
I decorated things a bit in there, but only to get the basic shape and background color to help us see what we’re doing. Notice how we save the width
value in a CSS variable. We’ll use that later. Not much to look at so far:
It looks like some sort of modern art experiment, right? Let’s introduce a new variable, --_r
, to store the circle’s radius, which is equal to half of the circle’s width. This way, if the width (--_w
) changes, the radius value (--_r
) will also update — thanks to another CSS math function, calc()
:
.clock {
--_w: 300px;
--_r: calc(var(--_w) / 2);
/* rest of styles */
}
Now, a bit of math. A circle is 360 degrees. We have 12 labels on our clock, so want to place the numbers every 30 degrees (360 / 12
). In math-land, a circle begins at 3 o’clock, so noon is actually minus 90 degrees from that, which is 270 degrees (360 - 90
).
Let’s add another variable, --_d
, that we can use to set a degree value for each number on the clock face. We’re going to increment the values by 30 degrees to complete our circle:
.clock time:nth-child(1) { --_d: 270deg; }
.clock time:nth-child(2) { --_d: 300deg; }
.clock time:nth-child(3) { --_d: 330deg; }
.clock time:nth-child(4) { --_d: 0deg; }
.clock time:nth-child(5) { --_d: 30deg; }
.clock time:nth-child(6) { --_d: 60deg; }
.clock time:nth-child(7) { --_d: 90deg; }
.clock time:nth-child(8) { --_d: 120deg; }
.clock time:nth-child(9) { --_d: 150deg; }
.clock time:nth-child(10) { --_d: 180deg; }
.clock time:nth-child(11) { --_d: 210deg; }
.clock time:nth-child(12) { --_d: 240deg; }
OK, now’s the time to get our hands dirty with the sin()
and cos()
functions! What we want to do is use them to get the X and Y coordinates for each number so we can place them properly around the clock face.
The formula for the X coordinate is radius + (radius * cos(degree))
. Let’s plug that into our new --_x
variable:
--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
The formula for the Y coordinate is radius + (radius * sin(degree))
. We have what we need to calculate that:
--_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
There are a few housekeeping things we need to do to set up the numbers, so let’s put some basic styling on them to make sure they are absolutely positioned and placed with our coordinates:
.clock-face time {
--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
--_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
--_sz: 12cqi;
display: grid;
height: var(--_sz);
left: var(--_x);
place-content: center;
position: absolute;
top: var(--_y);
width: var(--_sz);
}
Notice --_sz
, which we’ll use for the width
and height
of the numbers in a moment. Let’s see what we have so far.
This definitely looks more like a clock! See how the top-left corner of each number is positioned at the correct place around the circle? We need to “shrink” the radius when calculating the positions for each number. We can deduct the size of a number (--_sz
) from the size of the circle (--_w
), before we calculate the radius:
--_r: calc((var(--_w) - var(--_sz)) / 2);
Much better! Let’s change the colors, so it looks more elegant:
We could stop right here! We accomplished the goal of placing text around a circle, right? But what’s a clock without arms to show hours, minutes, and seconds?
Let’s use a single CSS animation for that. First, let’s add three more elements to our markup,
<div class="clock">
<!-- after <time>-tags -->
<span class="arm seconds"></span>
<span class="arm minutes"></span>
<span class="arm hours"></span>
<span class="arm center"></span>
</div>
Then some common markup for all three arms. Again, most of this is just make sure the arms are absolutely positioned and placed accordingly:
.arm {
background-color: var(--_abg);
border-radius: calc(var(--_aw) * 2);
display: block;
height: var(--_ah);
left: calc((var(--_w) - var(--_aw)) / 2);
position: absolute;
top: calc((var(--_w) / 2) - var(--_ah));
transform: rotate(0deg);
transform-origin: bottom;
width: var(--_aw);
}
We’ll use the same animation for all three arms:
@keyframes turn {
to {
transform: rotate(1turn);
}
}
The only difference is the time the individual arms take to make a full turn. For the hours arm, it takes 12 hours to make a full turn. The animation-duration
property only accepts values in milliseconds and seconds. Let’s stick with seconds, which is 43,200 seconds (60 seconds * 60 minutes * 12 hours
).
animation: turn 43200s infinite;
It takes 1 hour for the minutes arm to make a full turn. But we want this to be a multi-step animation so the movement between the arms is staggered rather than linear. We’ll need 60 steps, one for each minute:
animation: turn 3600s steps(60, end) infinite;
The seconds arm is almost the same as the minutes arm, but the duration is 60 seconds instead of 60 minutes:
animation: turn 60s steps(60, end) infinite;
Let’s update the properties we created in the common styles:
.seconds {
--_abg: hsl(0, 5%, 40%);
--_ah: 145px;
--_aw: 2px;
animation: turn 60s steps(60, end) infinite;
}
.minutes {
--_abg: #333;
--_ah: 145px;
--_aw: 6px;
animation: turn 3600s steps(60, end) infinite;
}
.hours {
--_abg: #333;
--_ah: 110px;
--_aw: 6px;
animation: turn 43200s linear infinite;
}
What if we want to start at the current time? We need a little bit of JavaScript:
const time = new Date();
const hour = -3600 * (time.getHours() % 12);
const mins = -60 * time.getMinutes();
app.style.setProperty('--_dm', `${mins}s`);
app.style.setProperty('--_dh', `${(hour+mins)}s`);
I’ve added id="app"
to the clockface and set two new custom properties on it that set a negative animation-delay
, as Mate Marschalko did when he shared a CSS-only clock. The getHours()
method of JavaScipt’s Date
object is using the 24-hour format, so we use the remainder
operator to convert it into 12-hour format.
In the CSS, we need to add the animation-delay
as well:
.minutes {
animation-delay: var(--_dm, 0s);
/* other styles */
}
.hours {
animation-delay: var(--_dh, 0s);
/* other styles */
}
Just one more thing. Using CSS @supports
and the properties we’ve already created, we can provide a fallback to browsers that do not supprt sin()
and cos()
. (Thank you, Temani Afif!):
@supports not (left: calc(1px * cos(45deg))) {
time {
left: 50% !important;
top: 50% !important;
transform: translate(-50%,-50%) rotate(var(--_d)) translate(var(--_r)) rotate(calc(-1*var(--_d)))
}
}
And, voilà ! Our clock is done! Here’s the final demo one more time. Again, it’s only supported in Firefox and Safari at the moment.
Just messing around here, but we can quickly turn our clock into a circular image gallery by replacing the <time>
tags with <img>
then updating the width (--_w
) and radius (--_r
) values:
Let’s try one more. I mentioned earlier how the clock looked kind of like a modern art experiment. We can lean into that and re-create a pattern I saw on a poster (that I unfortunately didn’t buy) in an art gallery the other day. As I recall, it was called “Moon” and consisted of a bunch of dots forming a circle.
We’ll use an unordered list this time since the circles don’t follow a particular order. We’re not even going to put all the list items in the markup. Instead, let’s inject them with JavaScript and add a few controls we can use to manipulate the final result.
The controls are range inputs (<input type="range">)
which we’ll wrap in a <form>
and listen for the input
event.
<form id="controls">
<fieldset>
<label>Number of rings
<input type="range" min="2" max="12" value="10" id="rings" />
</label>
<label>Dots per ring
<input type="range" min="5" max="12" value="7" id="dots" />
</label>
<label>Spread
<input type="range" min="10" max="40" value="40" id="spread" />
</label>
</fieldset>
</form>
We’ll run this method on “input”, which will create a bunch of <li>
elements with the degree (--_d
) variable we used earlier applied to each one. We can also repurpose our radius variable (--_r
) .
I also want the dots to be different colors. So, let’s randomize (well, not completely randomized) the HSL color value for each list item and store it as a new CSS variable, --_bgc
:
const update = () => {
let s = "";
for (let i = 1; i <= rings.valueAsNumber; i++) {
const r = spread.valueAsNumber * i;
const theta = coords(dots.valueAsNumber * i);
for (let j = 0; j < theta.length; j++) {
s += `<li style="--_d:${theta[j]};--_r:${r}px;--_bgc:hsl(${random(
50,
25
)},${random(90, 50)}%,${random(90, 60)}%)"></li>`;
}
}
app.innerHTML = s;
}
The random()
method picks a value within a defined range of numbers:
const random = (max, min = 0, f = true) => f ? Math.floor(Math.random() * (max - min) + min) : Math.random() * max;
And that’s it. We use JavaScript to render the markup, but as soon as it’s rendered, we don’t really need it. The sin()
and cos()
functions help us position all the dots in the right spots.
Placing things around a circle is a pretty basic example to demonstrate the powers of trigonometry functions like sin()
and cos()
. But it’s really cool that we are getting modern CSS features that provide new solutions for old workarounds I’m sure we’ll see way more interesting, complex, and creative use cases, especially as browser support comes to Chrome and Edge.
Creating a Clock with the New CSS sin() and cos() Trigonometry Functions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.