Archive

Archive for September, 2024

How to Make a “Scroll to Select” Form Control

September 25th, 2024 No comments

The  element is a fairly straightforward concept: focus on it to reveal a set of s that can be selected as the input’s value. That’s a great pattern and I’m not suggesting we change it. That said, I do enjoy poking at things and found an interesting way to turn a  into a dial of sorts — where options are selected by scrolling them into position, not totally unlike a combination lock or iOS date pickers. Anyone who’s expanded a  for selecting a country knows how painfully long lists can be and this could be one way to prevent that.

Here’s what I’m talking about:

CodePen Embed Fallback

It’s fairly common knowledge that styling  in CSS is not the easiest thing in the world. But here’s the trick: we’re not working with  at all. No, we’re not going to do anything like building our own  by jamming a bunch of JavaScript into a 

. We’re still working with semantic form controls, only it’s radio buttons.

<section class=scroll-container>
  <label for="madrid" class="scroll-item">
      Madrid 
      <abbr>MAD</abbr>
      <input id="madrid" type="radio" name="items">
  </label>
  <label for="malta" class="scroll-item">
      Malta 
      <abbr>MLA</abbr>
      <input id="malta" type="radio" name="items">
  </label>
  <!-- etc. -->
</section>

What we need is to style the list of selectable controls where we are capable of managing their sizes and spacing in CSS. I’ve gone with a group of labels with nested radio boxes as far as the markup goes. The exact styling is totally up to you, of course, but you can use these base styles I wrote up if you want a starting point.

.scroll-container {
  /* SIZING & LAYOUT */
  --itemHeight: 60px;
  --itemGap: 10px;
  --containerHeight: calc((var(--itemHeight) * 7) + (var(--itemGap) * 6));

  width: 400px; 
  height: var(--containerHeight);
  align-items: center;
  row-gap: var(--itemGap);
  border-radius: 4px;

  /* PAINT */
  --topBit: calc((var(--containerHeight) - var(--itemHeight))/2);
  --footBit: calc((var(--containerHeight) + var(--itemHeight))/2);

  background: linear-gradient(
    rgb(254 251 240), 
    rgb(254 251 240) var(--topBit), 
    rgb(229 50 34 / .5) var(--topBit), 
    rgb(229 50 34 / .5) var(--footBit), 
    rgb(254 251 240) 
    var(--footBit));
  box-shadow: 0 0 10px #eee;
}

A couple of details on this:

  • --itemHeight is the height of each item in the list.
  • --itemGap is meant to be the space between two items.
  • The --containerHeight variable is the .scroll-container’s height. It’s the sum of the item sizes and the gaps between them, ensuring that we display, at maximum, seven items at once. (An odd number of items gives us a nice balance where the selected item is directly in the vertical center of the list). 
  • The background is a striped gradient that highlights the middle area, i.e., the location of the currently selected item. 
  •  The --topBit and –-footBit variables are color stops that visually paint in the middle area (which is orange in the demo) to represent the currently selected item.

I’ll arrange the controls in a vertical column with flexbox declared on the .scroll-container:

.scroll-container {
  display: flex; 
  flex-direction: column;
  /* rest of styles */
}

With layout work done, we can focus on the scrolling part of this. If you haven’t worked with CSS Scroll Snapping before, it’s a convenient way to direct a container’s scrolling behavior. For example, we can tell the .scroll-container that we want to enable scrolling in the vertical direction. That way, it’s possible to scroll to the rest of the items that are not in view.

.scroll-container {
  overflow-y: scroll;
  /* rest of styles */
}

Next, we reach for the scroll-snap-style property that can be used to tell the .scroll-container that we want scrolling to stop on an item — not near an item, but directly on it.

.scroll-container {
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  /* rest of styles */
}

Now items “snap” onto an item instead of allowing a scroll to end wherever it wants. One more little detail I like to include is overscroll-behavior, specifically along the y-axis as far as this demo goes:

.scroll-container {
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  overscroll-behavior-y: none;
  /* rest of styles */
}

overscroll-behavior-y: none isn’t required to make this work, but when someone scrolls through the .scroll-container (along the y-axis), scrolling stops once the boundary is reached, and any further continued scrolling action will not trigger scrolling in any nearby scroll containers. Just a form of defensive CSS.

Time to move to the items inside the scroll container. But before we go there, here are some base styles for the items themselves that you can use as a starting point:

.scroll-item {
  /* SIZING & LAYOUT */
  width: 90%;
  box-sizing: border-box;
  padding-inline: 20px;
  border-radius: inherit; 

  /* PAINT & FONT */
  background: linear-gradient(to right, rgb(242 194 66), rgb(235 122 51));
  box-shadow: 0 0 4px rgb(235 122 51);
  font: 16pt/var(--itemHeight) system-ui;
  color: #fff;

  input { appearance: none; } 
  abbr { float: right; } /* The airport code */
}

As I mentioned earlier, the --itemHeight variable is setting as the size of each item and we’re declaring it on the flex property — flex: 0 0 var(--itemHeight). Margin is added before and after the first and last items, respectively, so that every item can reach the middle of the container through scrolling. 

The scroll-snap-align property is there to give the .scroll-container a snap point for the items. A center alignment, for instance, snaps an item’s center (vertical center, in this case) with the .scroll-container‘s center (vertical center as well). Since the items are meant to be selected through scrolling alone pointer-events: none is added to prevent selection from clicks.

One last little styling detail is to set a new background on an item when it is in a :checked state:

.scroll-item {
  /* Same styles as before */

  /* If input="radio" is :checked */
  &:has(:checked) {
    background: rgb(229 50 34);
  }
}

But wait! You’re probably wondering how in the world an item can be :checked when we’re removing pointer-events. Good question! We’re all finished with styling, so let’s move on to figuring some way to “select” an item purely through scrolling. In other words, whatever item scrolls into view and “snaps” into the container’s vertical center needs to behave like a typical form control selection. Yes, we’ll need JavaScript for that. 

let observer = new IntersectionObserver(entries => { 
  entries.forEach(entry => {
    with(entry) if(isIntersecting) target.children[1].checked = true;
  });
}, { 
  root: document.querySelector(`.scroll-container`), rootMargin: `-51% 0px -49% 0px`
});

document.querySelectorAll(`.scroll-item`).forEach(item => observer.observe(item));

The IntersectionObserver object is used to monitor (or “observe”) if and when an element (called a target) crosses through (or “intersects”) another element. That other element could be the viewport itself, but in this case, we’re observing the .scroll-container for when a .scroll-item intersects it. We’ve established the observed boundary with rootMargin:"-51% 0px -49% 0px".  

A callback function is executed when that happens, and we can use that to apply changes to the target element, which is the currently selected .scroll-item. In our case, we want to select a .scroll-item that is at the halfway mark in the .scroll-containertarget.children[1].checked = true.

That completes the code. Now, as we scroll through the items, whichever one snaps into the center position is the selected item. Here’s a look at the final demo again:

CodePen Embed Fallback

Let’s say that, instead of selecting an item that snaps into the .scroll-container‘s vertical center, the selection point we need to watch is the top of the container. No worries! All we do is update the scroll-snap-align property value from center to start in the CSS and remove the :first-of-type‘s top margin. From there, it’s only a matter of updating the scroll container’s background gradient so that the color stops highlight the top instead of the center. Like this:

CodePen Embed Fallback

And if one of the items has to be pre-selected when the page loads, we can get its position in JavaScript (getBoundingClientRect()) and use the scrollTo() method to scroll the container to where that specific item’s position is at the point of selection (which we’ll say is the center in keeping with our original demo). We’ll append a .selected class on that .scroll-item

<section class="scroll-container">
  <!-- more items -->
  <label class="scroll-items selected">
    2024
    <input type=radio name=items />
  </label>

  <!-- more items -->
</section>

Let’s select the .selected class, get its dimensions, and automatically scroll to it on page load:

let selected_item = (document.querySelector(".selected")).getBoundingClientRect();
let scroll_container = document.querySelector(".scroll-container");
scroll_container.scrollTo(0, selected_item.top - scroll_container.offsetHeight - selected_item.height);

It’s a little tough to demo this in a typical CodePen embed, so here’s a live demo in a GitHub Page (source code). I’ll drop a video in as well:

That’s it! You can build up this control or use it as a starting point to experiment with different layouts, styles, animations, and such. It’s important the UX clearly conveys to the users how the selection is done and which item is currently selected. And if I was doing this in a production environment, I’d want to make sure there’s a good fallback experience for when JavaScript might be unavailable and that my markup performs well on a screen reader.

References and further reading


How to Make a “Scroll to Select” Form Control originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

3 Essential Design Trends, October 2024

September 23rd, 2024 No comments

You have to get creative when you lack a strong visual. This month’s design trends roundup focuses on this concept.

Categories: Designing, Others Tags:

The Timeless Power Of Spreadsheets

September 23rd, 2024 No comments

Part of me can’t believe I’m writing this article. Applying the insights of Leonardo da Vinci or Saul Bass to web design is more my groove, but sometimes you simply have to write about spreadsheets. You have to advocate for them. Because someone should.

In a checkered career spanning copywriting, journalism, engineering, and teaching, I’ve seen time and time again how powerful and useful spreadsheets are in all walks of life. The cold, hard truth is that you — yes, you — likely have an enormous amount to gain by understanding how spreadsheets work. And, more importantly, how they can work for you.

That’s what this piece is about. It’s a rallying cry, with examples of spreadsheets’ myriad uses and how they can actually, in the right circumstances, be the bedrock of altogether inspiring, lovely things.

Cellular Organisms

Spreadsheets have been around for thousands of years. Papyrus remnants have been discovered from as far back as 4,600 BC. Their going digital in the late ‘70s was a major factor in the rise of personal computing. Much is (rightly) made of the cultural transformation brought about by the printing press. The digital spreadsheet, not so much.

For as long as people have had projects and data to organize, spreadsheets have been indispensable. They were the original databases.

Spreadsheets don’t always get a lot of attention these days. For organization and workflow, we usually find ourselves in the worlds of Trello, Jira, or GitHub Projects. Datasets live in Oracle, MongoDB, and the like. There are good reasons for these services emerging — everything has its place — but

I do get the sense that specialized tooling causes us to skip over the flexibility and power that today’s spreadsheet editors provide.

This is especially true for smaller projects and ones in their early stages. Yes, sometimes only a huge database will do, but often spreadsheets are more than fit for purpose.

Benefits

What makes spreadsheets so great? We’ll get into a few real-world examples in a second, but several qualities hold true. They include the following:

  • Collaboration
    Cloud-based editors like Google Sheets give groups of people a space in which to collaborate on data. They can serve as a middle ground for people working on different parts of the same project.
  • Structure
    It’s inherent to spreadsheets that they’ll get you thinking about the ‘shape’ of the information you’re dealing with. In the same way that a blank piece of paper invites fluidity of thought, tables coax out frameworks — and both have their place
  • Flexibility
    Spreadsheets can evolve in real time, which is especially useful during the formative stages of a project when the shape of the ‘data’ is still being established. Adding a field is as simple as naming a column, and the ability to weave in formulas makes it easy to infer other values from the ones you have. With stuff like the Google Sheets API, you can even scrape data directly from the spreadsheet
  • Power
    You’d be surprised how much you can do in spreadsheets. Sometimes, you don’t even need bespoke dashboards; you can do it all in the editor. From data visualization to pivot tables, spreadsheet editors come with a bunch of powerful out-of-the-box features.
  • They translate into other data formats
    Spreadsheets are one small jump from the mighty CSV. When the time is right, spreadsheets can still become raw data if you want them to.

Such is the flexibility and power of spreadsheets, and what’s listed here is scratching the surface. Their fundamental strength of organizing data has made them useful for thousands of years, while contemporary enhancements have taken them to the next level.

Case Studies

Below are a few examples from my own experiences that showcase these benefits in the real world. They’re obviously slanted towards my interests, but hopefully, they illustrate the usefulness of spreadsheets in different contexts.

Galaxies (Of The Guardian)

I work as a software engineer at Guardian News & Media, a place where 10% of the time, i.e., one work day every two weeks, is yours to spend on independent learning, side projects, and so on, is part of the working culture. An ongoing project of mine has been Galaxies (of the Guardian), a D3-powered org chart that represents departments as a series of interrelated people, teams, and streams.

What you see above is powered by information stored and edited in spreadsheets. A lambda scraps departmental information using the aforementioned Google Sheets API, then reformats into a shape Galaxies plays nicely with.

This approach has had several benefits. The earliest iterations of Galaxies were only possible because there was already a spreadsheet being maintained by those who needed to keep track of who worked where. Techies and non-techies alike are able to update information easily, and it is transparent to anyone who works inside the organization.

For anyone interested, I wrote a piece about how Galaxies works on the Guardian engineering blog. Suffice it to say here, spreadsheets were — and remain — the engine of the whole thing.

Food Bank Britain

My background is in journalism, and I still freelance in my own time. As my coding skills have improved, I’ve naturally gravitated towards data journalism, even teaching it for a year at my old journalism school.

Spreadsheets are inseparable from a lot of modern journalism — and, indeed, copyrighting in general. The digital world is awash with data, and good luck making sense of it without a working knowledge of spreadsheets.

For example, a piece I wrote for the Byline Times about foodbanks earlier this year simply wouldn’t have been possible without spreadsheets. It was by collating data from the Trussell Trust, the Independent Food Aid Network, and national census reports that I was able to map out the sheer scale of the UK’s food bank network.

Granted, the map is more visually engaging. But then that’s the idea. It’s the same information, just presented more pointedly.

There are plenty of other instances of spreadsheets being instrumental at the Guardian alone. Typerighter, the newspaper’s automated house style checker, began life as a subeditor’s spreadsheet. User research and bug tracking for the new Feast cooking app, which I worked on during its formative stages, was tracked and discussed in spreadsheets.

And, of course, countless pieces of quality journalism at the Guardian and beyond continue to be powered by them.

Another Cell In The Table

If this piece has got you to at least consider learning more about spreadsheets and spreadsheet editors, you’re in luck. There are countless free learning resources available on the web. Here are a few excellent beginner videos to help you on your way:

As for spreadsheet editors, the big three these days are probably Google Sheets, Microsoft Excel, and LibreOffice Calc (for the open source devotees out there). They all work much the same way. And as you get comfortable with their functionality, new avenues will open.

Data is the lifeblood of the modern web, and spreadsheets remain one of the most accessible, flexible ways to organize, analyze, and share it. As I hope the examples I’ve shared with you show, spreadsheets aren’t inherently boring. They can be, but when used in the right ways, they become the engines of dynamic, impactful work.

The way they go is up to you.

Categories: Others Tags:

Quick Hit #21

September 20th, 2024 No comments

Seeing a lot more headlines decrying JavaScript and pumping up PHP. Always interesting to see which direction the front-end zeitgeist is leaning.


Quick Hit #21 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

CSSWG Minutes Telecon (2024-09-18)

September 20th, 2024 No comments

For the past two months, all my livelihood has gone towards reading, researching, understanding, writing, and editing about Anchor Positioning, and with many Almanac entries published and a full Guide guide on the way, I thought I was ready to tie a bow on it all and call it done. I know that Anchor Positioning is still new and settling in. The speed at which it’s moved, though, is amazing. And there’s more and more coming from the CSSWG!

That all said, I was perusing the last CSSWG minutes telecon and knew I was in for more Anchor Positioning when I came to the following resolution:

Whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree) (Issue #10526: When does anchor-scope “match” a name?)

Resolutions aren’t part of the specification or anything, but the strongest of indications about where they’re headed. So, I thought this was a good opportunity not only to take a peek at what we might get in anchor-scope and touch on other interesting bits from the telecon.

Remember that you can subscribe and read the full minutes on W3C.org. 🙂

What’s anchor-scope?

To register an anchor, we can give it a distinctive anchor-name and then absolutely positioned elements with a matching position-anchor are attached to it. Even though it may look like it, anchor-name doesn’t have to be unique — we may reuse an anchor element inside a component with the same anchor-name.

<ul>
   <li>
	<div class="anchor">Anchor 1</div>
	<div class="target">Target 1</div>
   </li>
   <li>
	<div class="anchor">Anchor 2</div>
	<div class="target">Target 2</div>
   </li>
   <li>
	<div class="anchor">Anchor 3</div>
	<div class="target">Target 3</div>
   </li>
</ul>

However, if we try to connect them with CSS,

.anchor {
  anchor-name: --my-anchor;
}

.target {
  position: absolute;
  position-anchor: --my-anchor;
  position-area: top right;
}

We get an unpleasant surprise where instead of each .anchor have their .target positioned at its top-right edge, meaning they all pile up on the last .anchor instance. We can see it better by rotating each target a little. You’ll want to check out the next demo in Chrome 125+ to see the behavior:

CodePen Embed Fallback

The anchor-scope property should make an anchor element only discoverable by targets in their individual subtree. So, the prior example would be fixed in the future like this:

.anchor {
  anchor-name: --my-anchor;
  anchor-scope: --my-anchor;
}

This is fairly straightforward — anchor-scope makes the anchor element available only in that specific subtree. But then we have to ask another question: What should the anchor-scope own scope be? We can’t have an anchor-scope-scope property and then an anchor-scope-scope-scope and so on… so which behavior should it be?

This is what started the conversation, initially from a GitHub issue:

When an anchor-scope is specified with a , it scopes the name to that subtree when the anchor name is “matching”. The problem is that this matching can be interpreted in at least three ways: (Assuming that anchor-scope is a tree-scoped reference, which is also not clear in the spec):

  1. It matches by the ident part of the name only, ignoring any tree-scope that would be associated with the name, or
  2. It matches by exact match of the ident part and the associated tree-scope, or
  3. It matches by some mechanism similar to dereferencing of tree-scoped references, where it’s a match when the tree-scope of the anchor-scope-name is an inclusive ancestor of the tree-scope of the anchor query.

And then onto the CSSWG Minutes:

TabAtkins: In anchor positioning, anchor names and references are tree scoped. The anchor-scope property that scopes, does not say whether the names are tree scoped or not. Question to decide: should they be?

TabAtkins: I think the answer should be yes. If you have an anchor in a shadow tree with a part involved, then problems result if anchor scopes are not tree scoped. This is bad, so I think it should be tree scoped sounds pretty reasonable makes sense to me as far as I can understand it 🙂

This solution of the scope of scoping properties expanded towards View Transitions, which also rely on tree scoping to work:

khush: Thinking about this in the context of view transitions: in that API you give names and the tree scope has to be the same for them to match. There is another view transitions feature where I’m not sure if the spec says it’s tree scoped

khush: Want to make sure that feature is covered by the more general resolution

TabAtkins: Proposed more general resolution: whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree)

So the scope of anchor-scope is tree-scoped. Say that five times fast!

RESOLVED: whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree)

The next resolution was pretty straightforward. Besides allowing a , the anchor-scope property can take an all keyword, which means that all anchors are tree-scoped, while the says that specific anchor is three-scoped. So, the question was if all is also a tree-scoped value.

TabAtkins: anchor-scope, in addition to idents, can take the keyword ‘all‘, which scopes all names. Should this be a tree-scoped ‘all‘? (i.e. only applies to the current tree scope)

TabAtkins: Proposed resolution: the ‘all‘ keyword is also tree-scoped in the same way sgtm +1, again same pattern with view-transition-group

RESOLVED: the ‘all‘ keyword is tree-scoped

The conversation switched gears toward new properties coming in the CSS Scroll Snap Module Level 2 draft, which is all about changing the user’s initial scroll with CSS. Taking anexample directly from the spec, say we have an image carousel:

<div class="carousel">
  <img src="img1.jpg">
  <img src="img2.jpg">
  <img src="img3.jpg" class="origin">
  <img src="img4.jpg">
  <img src="img5.jpg">
</div>

We could start our scroll to show another image by setting it’s scroll-start-targe to auto:

.carousel {
  overflow-inline: auto;
}

.carousel .origin {
  scroll-start-target: auto;
}

As of right now, the only way to complete this is using JavaScript to scroll an element into view:

document.querySelector(".origin").scrollIntoView({
  behavior: "auto",
  block: "center",
  inline: "center"
});

The last example is probably a carousel that is only scrollable in the inline direction. Still, there are doubts as far when the container is scrollable in both the inline and block directions. As seen in the initial GitHub issue:

The scroll snap 2 spec says that when there are multiple elements that could be scroll-start-targets for a scroll container “user-agents should select the one which comes first in tree order“.

Selecting the first element in tree-order seems like a natural way to resolve competition between multiple targets which would be scrolled to in one particular axis but is perhaps not as flexible as might be needed for the 2d case where an author wants to scroll to one item in one axis and another item in the other axis.

And back to the CSSWG minutes:

DavidA: We have a property we’re adding called scroll-start-target that indicates if an element within a scroll container, then the scroll should start with that element onscreen. Question is what happens if there are multiple targets?
DavidA: Propose to do it in reverse-DOM order, this would result in the first one applied last and then be on screen. Also, should only change the scroll position if you have to.

After discussing why we have to define scroll-start-target when we have scroll-snap-align, the discussion went on discuss the reverse-DOM order:

fantasai: There was a bunch of discussion about regular vs reverse-DOM order. Where did we end up and why?
flackr: Currently, we expect that it scrolls to the first item in DOM order. We probably want that to still happen. That is why the proposal is to scroll to each item in sequence in reverse-DOM order.

So we are coming in reverse to scroll the element, but only as required so the following elements are showing as much as possible:

flackr: There is also the issue of nearest…
fantasai: Can you explain nearest?
flackr: Same as scroll into view
fantasai: ?
flackr: This is needed with you scroll multiple things into view and want to find a good position (?)
fantasai: You scroll in reverse-DOM order…when you add the spec can you make it really clear that this is the end result of the algorithm?
flackr: Yes absolutely
fantasai: Otherwise it seems to make sense

And so it was resolved:

Proposed resolution 2: When scroll-start-target targets multiple elements, scroll to each in reverse DOM order with text to specify priority is the first item

Lastly, there was the debate about the text-underline-position, that when set to auto says, “The user agent may use any algorithm to determine the underline’s position; however it must be placed at or under the alphabetic baseline.” The discussion was about whether the auto value should automatically adjust the underlined position to match specific language rules, for example, at the right of the text for vertical writing modes, like Japanese and Mongolian.

fantasai: The initial value of text-underline-position is auto, which is defined as “find a good place to put the underline”.
Three options there: (1) under alphabetical baseline, (2) fully below text (good for lots-of-descenders cases), (3) for vertical text on the RHS
fantasai: auto value is defined in the spec about ‘how far down below the text’, but doesn’t say things about flipping. The current spec says “at or below”. In order to handle language-specific aspects, there is a default UA style sheet that for Chinese and Japanese and Korean there are differences for those languages. A couple of implementations do this
fantasai: Should we change the spec to mention these things?
fantasai: Or should we stick with the UA stylesheet approach?

The thing is that Chrome and Firefox already place the underline on the right in vertical Japanese when text-underline-position is auto.

CodePen Embed Fallback

The group was left wiuth three options:

A) Keep spec as-is, update Gecko + Blink to match (using UA stylesheet for language switch)
B) Introduce auto to text-emphasis-position and use it in both text-emphasis-position and text-underline-position to effect language switches
C) Adopt inconsistent behavior: text-underline-position uses ‘auto‘ and text-emphasis-position uses UA stylesheet

Many CSSWG members like Emilio Cobos, TabAtkins, Miriam Suzanne, Rachel Andrew and fantasai casted their votes, resulting in the following resolution:

RESOLVED: add auto value for text-emphasis-position, and change the meaning of text-underline-position: auto to care about left vs right in vertical text

I definitely encourage you to read at the full minutes! Or if you don’t have the time, you can there’s a list just of resolutions.


CSSWG Minutes Telecon (2024-09-18) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

Staying Relevant: A Guide for Brands in the Era of Gen Z Trends

September 20th, 2024 No comments

In today’s fast-paced digital world, brands constantly face the challenge of staying relevant to younger consumers. For Gen Z, traditional marketing strategies often fall flat. This age group are as brand-savvy as any top marketer and members of this generation engage in a personal branding project by building online personas that are as integral to their identity as any offline habits.

Gen Z is a switched-on, brand-cynical generation. To connect with this age group, you must understand them, and the unstable world they’ve built their identity in. A life online and unstable social, political and economic boundaries have led this generation to be drawn to short-term trends and memes that resonate with their sense of nostalgia, often not for distant decades, but for more recent times.

The exploding “brat summer” trend, with its early-naughties low-fi aesthetic, is the perfect example of this. This viral trend has quickly reached every corner of our culture and even initiated a high-profile ‘rebrand’ for the Kamala Harris presidential campaign.

If it’s good enough for a presidential contender, it should be good enough for your brand. But as the aesthetic of ‘Brat Summer’ compresses a nostalgic ache for simpler times with the accelerated trend-slash-meme production of the internet machine, it’s tough for brands to keep up.

Nostalgia branding might be about looking backward, but Gen Z is creating short-term nostalgic trends in a very forward-thinking way. Let’s find out how your brand can leverage the what’s trending treadmill to engage with a younger audience.

Why Nostalgia Branding is so Powerful for a Gen Z Audience

Nostalgia branding taps into powerful emotions, making it an effective strategy for connecting with any age group: 77% of consumers call this evergreen branding trend interesting. By evoking fond memories and a sense of comfort, consumers build a strong connection to the brand in question. This has traditionally been done by leaning on older eras: the ‘70s, ’80s or ’90s, and by targeting a time that your target audience remembers.

However, nostalgia hits different for Gen Z, a generation raised online with a mismatched set of cultural references at their fingertips. Despite being born long after ‘80s beach culture exemplified by sunglasses brand Vacation, the visual aesthetic is instantly understood. But Gen Z aren’t just drawn to long-term nostalgic vision: they’re also nostalgic for elements of their own formative years, and even the very recent past.

A Shorter Cycle of Nostalgia Trends

The internet has accelerated the cycle of trends and the present turns into the past quicker than ever, making it ripe for nostalgia, and nostalgia branding. What used to take decades to become nostalgic now takes just a few years. Social media platforms like TikTok, Instagram, and Twitter have created environments where trends can go viral and fade away in the blink of an eye. This creates opportunities for brands to quickly adapt and leverage these trends.

“Nostalgia” can be for anything now: while previous efforts to implement nostalgia in branding have been casting back decades for eras where different value systems seemed more prominent (eg family, freedom, etc), now, Gen Z (and to an extent Millennials) will lean into an almost immediate nostalgia for things that happened in the recent past: Brat’s aesthetic is for an early-internet era, a simpler time.

Brat: A Case Study in Recent Nostalgia

“Brat” is a trend that encapsulates Gen Z’s unique take on nostalgia. It draws inspiration from early 2000s pop culture, particularly the rebellious and carefree attitudes seen in movies, TV shows, and music from that era. Think mini skirts, chunky highlights, and a vibe of spirited defiance.

Nobody expects it to last forever, with 80% of 18-25s anticipating this trend sticking around for fewer than a few months, but that’s part of the point. This should guide brand strategy: be fun, carefree and colorful, but be ready to move on to keep up with your audience.

Brands can take a leaf out of the “brat summer” playbook by understanding what resonates with Gen Z. They appreciate the irony and humor in looking back at trends from a decade ago as if they were ancient history. By tapping into this recent nostalgia, brands can create content that feels both familiar and fresh.

How Brands Can Leverage Online Trends to Engage Audiences

An accelerated cycle of memes and trends feeds into a potent nostalgic cocktail that can create highly effecting, and effective, marketing strategies, but it’s also overwhelming for brands trying to keep up.

Nevertheless, the opportunity for engagement, as well as creating a sense of authenticity, is huge when you successfully tap into short-term cycles of internet nostalgia. Here’s how….

1. Stay on Top of Trends

Keep an eye on social media platforms where trends emerge. Tools like Google Trends, Twitter’s trending topics, and TikTok’s Discover page can help you stay up to date.

While the specifics of a trend can’t be anticipated — nobody expected the neon tones and lower-case typography of Brat — the “something” summer is now a well-established template. Hot girl summer and hot vax summer are in the rearview mirror, and Brat summer is turning up the heat this July. Brands should be aware of wider trends and be ready to leverage memes and microtrends that fit.

2. Be Authentic

Faced with an increasingly superficial online world, Gen Z values authenticity: both in the brands they love and in their own lives. When leveraging nostalgia, it’s important to do so in a way that feels genuine. Avoid pandering, and instead, create content that creates relevance between your brand and a nostalgic trend, like Old Spice’s old-timey barber shop.

3. Collaborate with Influencers

Influencers who resonate with Gen Z can help amplify your message. Partner with influencers who align with the trend to create sponsored content that feels organic and relatable. A collaboration between luxury fashion brand Marc Jacobs and a TikTok account recreating soap opera drama with Sylvanian Families was one recent hit among a Gen Z audience.

4. Use Visual Storytelling

Visual platforms like Instagram and TikTok are perfect for nostalgia branding where past aesthetics can be used to create arresting visual content, such as Vacation’s consistent, sun-kissed ‘70s look. Nostalgia is about evoking an emotional response to times gone by, but given that half our brain’s processing power is devoted to the visual, this is best done with powerful imagery.

5. Engage in Conversations

By hitching your wagon to the latest trends, your brand is joining the conversation. Don’t forget to comment on posts, share user-generated content, and use hashtags to make your brand part of the community. By engaging in a meaningful way, you’ll ensure you’re not just responding to the latest trends, but playing your part in creating the next one.

Conclusion

Brands must be careful when leveraging short-term trends to build long-term engagement. While younger consumers see the Brat aesthetic everywhere, we found that less than 6% of over-45s are familiar with the trend. Know your audience before you lean into a meme otherwise, your efforts could fall flat.

For younger consumers, however, tapping into timely trends creates a perception of authenticity in the brands they see online. Connecting with Gen Z requires an understanding of their unique relationship with trends and nostalgia. Short-term trends like “brat summer” offer a glimpse into how recent nostalgia can be a powerful tool for engagement.

By staying ahead of trends, being authentic, collaborating with influencers, creating interactive content, using visual storytelling, and engaging in conversations, brands can build meaningful connections with younger consumers.

Featured image by Brooke Lark on Unsplash

The post Staying Relevant: A Guide for Brands in the Era of Gen Z Trends appeared first on noupe.

Categories: Others Tags:

Quick Hit #20

September 19th, 2024 No comments

Having fun with Bramus’ new Caniuse CLI tool. This’ll save lots of trips to the Caniuse site!


Quick Hit #20 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

Quick Hit #19

September 19th, 2024 No comments

Two possible syntaxes for CSS masonry, one draft specification, and you get to share your opinions.


Quick Hit #19 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

A Beginner’s Guide to Using BlueSky for Business Success

September 19th, 2024 No comments

In today’s fast-paced digital world, businesses are always on the lookout for new ways to connect with their audience. BlueSky, a decentralized social media platform, is quickly gaining attention as a fresh alternative to traditional platforms like Twitter and Instagram. While it’s still early days for BlueSky, its unique structure offers a big opportunity for businesses to build communities and engage with users in a more transparent, user-focused way.

Categories: Designing, Others Tags:

SVG Coding Examples: Useful Recipes For Writing Vectors By Hand

September 18th, 2024 No comments

Even though I am the kind of front-end engineer who manually cleans up SVG files when they are a mess, I never expected to become one of those people. You know, those crazy people that draw with code.

But here we are.

I dove deep into SVG specs last winter when I created a project to draw Calligraphy Grids, and even though I knew the basic structures and rules of SVG, it was only then that I fully tried to figure out and understand what all of those numbers meant and how they interacted with each other.

And, once you get the hang of it, it is actually very interesting and quite fun to code SVG by hand.

No ahead

We won’t go into more complex SVG shapes like paths in this article, this is more about practical information for simple SVGs. When it comes to drawing curves, I still recommend using a tool like Illustrator or Affinity. However, if you are super into compounding your lines, a path is useful. Maybe we’ll do that in Part 2.

Also, this guide focuses mostly on practical examples that illustrate some of the math involved when drawing SVGs. There is a wonderful article here that goes a bit deeper into the specs, which I recommend reading if you’re more interested in that: “A Practical Guide To SVG And Design Tools.”

Drawing With Math. Remember Coordinate Systems?

Illustrator, Affinity, and all other vector programs are basically just helping you draw on a coordinate system, and then those paths and shapes are stored in SVG files.

If you open up these files in an editor, you’ll see that they are just a bunch of paths that contain lots of numbers, which are coordinates in that coordinate system that make up the lines.

But, there is a difference between the all-powerful and the other, more semantic elements like , , , , , and .

These elements are not that hard to read and write by hand, and they open up a lot of possibilities to add animation and other fun stuff. So, while most people might only think of SVGs as never-pixelated, infinitely scaling images, they can also be quite comprehensive pieces of code.

How Does SVG Work? unit != unit

Before we get started on how SVG elements are drawn, let’s talk about the ways units work in SVG because they might be a bit confusing when you first get started.

The beauty of SVG is that it’s a vector format, which means that the units are somewhat detached from the browser and are instead just relative to the coordinate system you’re working in.

That means you would not use a unit within SVG but rather just use numbers and then define the size of the document you’re working with.

So, your width and height might be using CSS rem units, but in your viewBox, units become just a concept that helps you in establishing sizing relationships.

What Is The viewBox?

The viewBox works a little bit like the CSS aspect-ratio property. It helps you establish a relationship between the width and the height of your coordinate system and sets up the box you’re working in. I tend to think of the viewBox as my “document” size.

Any element that is placed within the SVG with bigger dimensions than the viewBox will not be visible. So, the viewBox is the cutout of the coordinate system we’re looking through. The width and height attributes are unnecessary if there is a viewBox attribute.

So, in short, having an SVG with a viewBox makes it behave a lot like a regular image. And just like with images, it’s usually easiest to just set either a width or a height and let the other dimension be automatically sized based on the intrinsic aspect ratio dimensions.

So, if we were to create a function that draws an SVG, we might store three separate variables and fill them in like this:

`<svg 
  width="${svgWidth}" 
  viewBox="0 0 ${documentWidth} ${documentHeight}" 
  xmlns="http://www.w3.org/2000/svg"
>`;

SVG Things Of Note

There is a lot to know about SVG: When you want to reuse an image a lot, you may want to turn it into a symbol that can then be referenced with a use tag, you can create sprites, and there are some best practices when using them for icons, and so on.

Unfortunately, this is a bit out of the scope of this article. Here, we’re mainly focusing on designing SVG files and not on how we can optimize and use them.

However, one thing of note that is easier to implement from the start is accessibility.

SVGs can be used in an tag, where alt tags are available, but then you lose the ability to interact with your SVG code, so inlining might be your preference.

When inlining, it’s easiest to declare role="img" and then add a </code> tag with your image title.</p> <p><strong>Note</strong>: <em>You can check out <a target="_blank" href="https://www.smashingmagazine.com/2021/05/accessible-svg-patterns-comparison/" rel="noopener">this article for SVG and Accessibility recommendations</a>.</em></p> <pre><code><svg role="img" [...attr] > <title>An accessible title</title> <!-- design code --> </svg> </code></pre> <p>Drawing SVG With JavaScript</p> <p>There is usually some mathematics involved when drawing SVGs. It’s usually fairly simple arithmetic (except, you know, in case you draw calligraphy grids and then have to dig out trigonometry…), but I think even for simple math, most people don’t write their SVGs in pure HTML and thus would like to use algebra.</p> <p>At least for me, I find it much easier to understand SVG Code when giving meaning to numbers, so I always stick to JavaScript, and by giving my coordinates names, I like them immeasurable times more.</p> <p>So, for the upcoming examples, we’ll look at the list of variables with the simple math and then JSX-style templates for interpolation, as that gives more legible syntax highlighting than string interpolations, and then each example will be available as a CodePen.</p> <p>To keep this Guide framework-agnostic, I wanted to quickly go over drawing SVG elements with just good old vanilla JavaScript.</p> <p>We’ll create a container element in HTML that we can put our SVG into and grab that element with JavaScript.</p> <pre><code><div data-svg-container></div> <script src="template.js"></script> </code></pre> <p>To make it simple, we’ll draw a rectangle <code></code> that covers the entire <code>viewBox</code> and uses a fill.</p> <p><strong>Note</strong>: <em>You can add all valid CSS values as fills, so a fixed color, or something like <code>currentColor</code> to access the site’s text color or a CSS variable would work here if you’re inlining your SVG and want it to interact with the page it’s placed in.</em></p> <p>Let’s first start with our variable setup.</p> <div> <pre><code>// vars const container = document.querySelector("[data-svg-container]"); const svgWidth = "30rem"; // use any value with units here const documentWidth = 100; const documentHeight = 100; const rectWidth = documentWidth; const rectHeight = documentHeight; const rectFill = "currentColor"; // use any color value here const title = "A simple square box"; </code></pre> </div> <h3>Method 1: Create Element and Set Attributes</h3> <p>This method is easier to keep type-safe (if using TypeScript) — uses proper SVG elements and attributes, and so on — but it is less performant and may take a long time if you have many elements.</p> <div> <pre><code>const svg = document.createElementNS("<a target="_blank" href="http://www.w3.org/2000/svg%22" rel="noopener">http://www.w3.org/2000/svg"</a>, "svg"); const titleElement = document.createElementNS("<a target="_blank" href="http://www.w3.org/2000/svg%22" rel="noopener">http://www.w3.org/2000/svg"</a>, "title"); const rect = document.createElementNS("<a target="_blank" href="http://www.w3.org/2000/svg%22" rel="noopener">http://www.w3.org/2000/svg"</a>, "rect"); svg.setAttribute("width", svgWidth); svg.setAttribute("viewBox", <code>0 0 ${documentWidth} ${documentHeight}</code>); svg.setAttribute("xmlns", "<a target="_blank" href="http://www.w3.org/2000/svg%22)" rel="noopener">http://www.w3.org/2000/svg")</a>; svg.setAttribute("role", "img"); titleElement.textContent = title; rect.setAttribute("width", rectWidth); rect.setAttribute("height", rectHeight); rect.setAttribute("fill", rectFill); svg.appendChild(titleElement); svg.appendChild(rect); container.appendChild(svg); </code></pre> </div> <p><img decoding="async" src="https://files.smashing.media/articles/svg-coding-examples-recipes-writing-vectors-by-hand/polygon-polyline-composite-fixed.png"></p> <p>Here, you can see that with the same coordinates, a polyline won’t draw the line between the blue and the red dot, while a polygon will. However, when applying a fill, they take the exact same information as if the shape was closed, which is the right side of the graphic, where the polyline makes it look like a piece of a circle is missing.</p> <p>This is the second time where we have dealt with quite a bit of repetition, and we can have a look at how we could leverage the power of JavaScript logic to render our template faster.</p> <p>But first, we need a basic implementation like we’ve done before. We’re creating objects for the circles, and then we’re chaining the <code>cx</code> and <code>cy</code> values together to create the <code>points</code> attribute. We’re also storing our transforms in variables.</p> <div> <pre><code>const polyDocWidth = 200; const polyDocHeight = 200; const circleOne = { cx: 25, cy: 80, r: 10, fill: "red" }; const circleTwo = { cx: 40, cy: 20, r: 5, fill: "lime" }; const circleThree = { cx: 70, cy: 60, r: 8, fill: "cyan" }; const points = <code>${circleOne.cx},${circleOne.cy} ${circleTwo.cx},${circleTwo.cy} ${circleThree.cx},${circleThree.cy}</code>; const moveToTopRight = <code>translate(${polyDocWidth / 2}, 0)</code>; const moveToBottomRight = <code>translate(${polyDocWidth / 2}, ${polyDocHeight / 2})</code>; const moveToBottomLeft = <code>translate(0, ${polyDocHeight / 2})</code>; </code></pre> </div> <p>And then, we apply the variables to the template, using either a <code>polyline</code> or <code>polygon</code> element and a <code>fill</code> attribute that is either set to <code>none</code> or a color value.</p> <pre><code> <svg width={svgWidth} viewBox={`0 0 ${polyDocWidth} ${polyDocHeight}`} xmlns="http://www.w3.org/2000/svg" role="img" > <title>Composite shape comparison</title> <g> <circle cx={circleOne.cx} cy={circleOne.cy} r={circleOne.r} fill={circleOne.fill} /> <circle cx={circleTwo.cx} cy={circleTwo.cy} r={circleTwo.r} fill={circleTwo.fill} /> <circle cx={circleThree.cx} cy={circleThree.cy} r={circleThree.r} fill={circleThree.fill} /> <polyline points={points} fill="none" stroke="black" /> </g> <g transform={moveToTopRight}> <circle cx={circleOne.cx} cy={circleOne.cy} r={circleOne.r} fill={circleOne.fill} /> <circle cx={circleTwo.cx} cy={circleTwo.cy} r={circleTwo.r} fill={circleTwo.fill} /> <circle cx={circleThree.cx} cy={circleThree.cy} r={circleThree.r} fill={circleThree.fill} /> <polyline points={points} fill="white" stroke="black" /> </g> <g transform={moveToBottomLeft}> <circle cx={circleOne.cx} cy={circleOne.cy} r={circleOne.r} fill={circleOne.fill} /> <circle cx={circleTwo.cx} cy={circleTwo.cy} r={circleTwo.r} fill={circleTwo.fill} /> <circle cx={circleThree.cx} cy={circleThree.cy} r={circleThree.r} fill={circleThree.fill} /> <polygon points={points} fill="none" stroke="black" /> </g> <g transform={moveToBottomRight}> <circle cx={circleOne.cx} cy={circleOne.cy} r={circleOne.r} fill={circleOne.fill} /> <circle cx={circleTwo.cx} cy={circleTwo.cy} r={circleTwo.r} fill={circleTwo.fill} /> <circle cx={circleThree.cx} cy={circleThree.cy} r={circleThree.r} fill={circleThree.fill} /> <polygon points={points} fill="white" stroke="black" /> </g> </svg> </code></pre> <p>And here’s a version of it to play with:</p> <p>See the Pen <a target="_blank" href="https://codepen.io/smashingmag/pen/OJeeVoM" rel="noopener">SVG Polygon / Polyline (simple) [forked]</a> by <a target="_blank" href="https://codepen.io/mynimi" rel="noopener">Myriam</a>.</p> <p>Dealing With Repetition</p> <p>When it comes to drawing SVGs, you may find that you’ll be repeating a lot of the same code over and over again. This is where JavaScript can come in handy, so let’s look at the composite example again and see how we could optimize it so that there is less repetition.</p> <p><strong>Observations:</strong></p> <ul> <li>We have three circle elements, all following the same pattern.</li> <li>We create one repetition to change the <code>fill</code> style for the element.</li> <li>We repeat those two elements one more time, with either a <code>polyline</code> or a <code>polygon</code>.</li> <li>We have four different <code>transforms</code> (technically, no transform is a transform in this case).</li> </ul> <p>This tells us that we can create nested loops.</p> <p>Let’s go back to just a vanilla implementation for this since the way loops are done is quite different across frameworks.</p> <p>You could make this more generic and write separate generator functions for each type of element, but this is just to give you an idea of what you could do in terms of logic. There are certainly still ways to optimize this.</p> <p>I’ve opted to have arrays for each type of variation that we have and wrote a helper function that goes through the data and builds out an array of objects with all the necessary information for each group. In such a short array, it would certainly be a viable option to just have the data stored in one element, where the values are repeated, but we’re taking the DRY thing seriously in this one.</p> <p>The group array can then be looped over to build our SVG HTML.</p> <div> <pre><code>const container = document.querySelector("[data-svg-container]"); const svgWidth = 200; const documentWidth = 200; const documentHeight = 200; const halfWidth = documentWidth / 2; const halfHeight = documentHeight / 2; const circles = [ { cx: 25, cy: 80, r: 10, fill: "red" }, { cx: 40, cy: 20, r: 5, fill: "lime" }, { cx: 70, cy: 60, r: 8, fill: "cyan" }, ]; const points = circles.map(({ cx, cy }) => <code>${cx},${cy}</code>).join(" "); const elements = ["polyline", "polygon"]; const fillOptions = ["none", "white"]; const transforms = [ undefined, <code>translate(${halfWidth}, 0)</code>, <code>translate(0, ${halfHeight})</code>, <code>translate(${halfWidth}, ${halfHeight})</code>, ]; const makeGroupsDataObject = () => { let counter = 0; const g = []; elements.forEach((element) => { fillOptions.forEach((fill) => { const transform = transforms[counter++]; g.push({ element, fill, transform }); }); }); return g; }; const groups = makeGroupsDataObject(); // result: // [ // { // element: "polyline", // fill: "none", // }, // { // element: "polyline", // fill: "white", // transform: "translate(100, 0)", // }, // { // element: "polygon", // fill: "none", // transform: "translate(0, 100)", // }, // { // element: "polygon", // fill: "white", // transform: "translate(100, 100)", // } // ] const svg = document.createElementNS("<a target="_blank" href="http://www.w3.org/2000/svg%22" rel="noopener">http://www.w3.org/2000/svg"</a>, "svg"); svg.setAttribute("width", svgWidth); svg.setAttribute("viewBox", <code>0 0 ${documentWidth} ${documentHeight}</code>); svg.setAttribute("xmlns", "<a target="_blank" href="http://www.w3.org/2000/svg%22)" rel="noopener">http://www.w3.org/2000/svg")</a>; svg.setAttribute("role", "img"); svg.innerHTML = "<title>Composite shape comparison</title>"; groups.forEach((groupData) => { const circlesHTML = circles .map((circle) => { return <code>&lt;circle cx="${circle.cx}" cy="${circle.cy}" r="${circle.r}" fill="${circle.fill}" /&gt;</code>; }) .join(""); const polyElementHTML = <code>&lt;${groupData.element} points="${points}" fill="${groupData.fill}" stroke="black" /&gt;</code>; const group = <code>&lt;g ${groupData.transform ?</code>transform="${groupData.transform}"<code>: ""}&gt; ${circlesHTML} ${polyElementHTML} &lt;/g&gt;</code>; svg.innerHTML += group; }); container.appendChild(svg); </code></pre> </div> <p>And here’s the Codepen of that:</p> <p>See the Pen <a target="_blank" href="https://codepen.io/smashingmag/pen/XWLLbPq" rel="noopener">SVG Polygon / Polyline (JS loop version) [forked]</a> by <a target="_blank" href="https://codepen.io/mynimi" rel="noopener">Myriam</a>.</p> <p>More Fun Stuff</p> <p>Now, that’s all the basics I wanted to cover, but there is so much more you can do with SVG. There is more you can do with <code>transform</code>; you can use a <code>mask</code>, you can use a <code>marker</code>, and so on.</p> <p>We don’t have time to dive into all of them today, but since this started for me when making Calligraphy Grids, I wanted to show you the two most satisfying ones, which I, unfortunately, can’t use in the generator since I wanted to be able to open my generated SVGs in Affinity and it doesn’t support <code>pattern</code>.</p> <p>Okay, so <code>pattern</code> is part of the <code>defs</code> section within the SVG, which is where you can define reusable elements that you can then reference in your SVG.</p> <h3>Graph Grid with <code>pattern</code><br /> </h3> <p>If you think about it, a graph is just a bunch of horizontal and vertical lines that repeat across the x- and y-axis.</p> <p>So, <code>pattern</code> can help us with that. We can create a <code></code> and then reference a <code>pattern</code> in the <code>fill</code> attribute of the <code>rect</code>. The pattern then has its own <code>width</code>, <code>height</code>, and <code>viewBox</code>, which defines how the pattern is repeated.</p> <p>So, let’s say we want to perfectly center our graph grid in any given width or height, and we want to be able to define the size of our resulting squares (cells).</p> <p>Once again, let’s start with the JavaScipt variables:</p> <pre><code>const graphDocWidth = 226; const graphDocHeight = 101; const cellSize = 5; const strokeWidth = 0.3; const strokeColor = "currentColor"; const patternHeight = (cellSize / graphDocHeight) * 100; const patternWidth = (cellSize / graphDocWidth) * 100; const gridYStart = (graphDocHeight % cellSize) / 2; const gridXStart = (graphDocWidth % cellSize) / 2; </code></pre> <p>Now, we can apply them to the SVG element:</p> <pre><code><svg width={svgWidth} viewBox={`0 0 ${graphDocWidth} ${graphDocHeight}`} xmlns="http://www.w3.org/2000/svg" role="img" > <defs> <pattern id="horizontal" viewBox={`0 0 ${graphDocWidth} ${strokeWidth}`} width="100%" height={`${patternHeight}%`} > <line x1="0" x2={graphDocWidth} y1={gridYStart} y2={gridYStart} stroke={strokeColor} stroke-width={strokeWidth} /> </pattern> <pattern id="vertical" viewBox={`0 0 ${strokeWidth} ${graphDocHeight}`} width={`${patternWidth}%`} height="100%" > <line y1={0} y2={graphDocHeight} x1={gridXStart} x2={gridXStart} stroke={strokeColor} stroke-width={strokeWidth} /> </pattern> </defs> <title>A graph grid</title> <rect width={graphDocWidth} height={graphDocHeight} fill="url(#horizontal)" /> <rect width={graphDocWidth} height={graphDocHeight} fill="url(#vertical)" /> </svg> </code></pre> <p>And this is what that then looks like:</p> <p>See the Pen <a target="_blank" href="https://codepen.io/smashingmag/pen/XWLLbxq" rel="noopener">SVG Graph Grid [forked]</a> by <a target="_blank" href="https://codepen.io/mynimi" rel="noopener">Myriam</a>.</p> <h3>Dot Grid With <code>pattern</code><br /> </h3> <p>If we wanted to draw a dot grid instead, we could simply repeat a circle. Or, we could alternatively use a line with a <code>stroke-dasharray</code> and <code>stroke-dashoffset</code> to create a dashed line. And we’d only need one line in this case.</p> <p>Starting with our JavaScript variables:</p> <pre><code>const dotDocWidth = 219; const dotDocHeight = 100; const cellSize = 4; const strokeColor = "black"; const gridYStart = (dotDocHeight % cellSize) / 2; const gridXStart = (dotDocWidth % cellSize) / 2; const dotSize = 0.5; const patternHeight = (cellSize / dotDocHeight) * 100; </code></pre> <p>And then adding them to the SVG element:</p> <pre><code><svg width={svgWidth} viewBox={`0 0 ${dotDocWidth} ${dotDocHeight}`} xmlns="http://www.w3.org/2000/svg" role="img" > <defs> <pattern id="horizontal-dotted-line" viewBox={`0 0 ${dotDocWidth} ${dotSize}`} width="100%" height={`${patternHeight}%`} > <line x1={gridXStart} y1={gridYStart} x2={dotDocWidth} y2={gridYStart} stroke={strokeColor} stroke-width={dotSize} stroke-dasharray={`0,${cellSize}`} stroke-linecap="round" ></line> </pattern> </defs> <title>A Dot Grid</title> <rect x="0" y="0" width={dotDocWidth} height={dotDocHeight} fill="url(#horizontal-dotted-line)" ></rect> </svg> </code></pre> <p>And this is what that looks like:</p> <p>See the Pen <a target="_blank" href="https://codepen.io/smashingmag/pen/eYwwNQM" rel="noopener">SVG Dot Grid [forked]</a> by <a target="_blank" href="https://codepen.io/mynimi" rel="noopener">Myriam</a>.</p> <p>Conclusion</p> <p>This brings us to the end of our little introductory journey into SVG. As you can see, coding SVG by hand is not as scary as it seems. If you break it down into the basic elements, it becomes quite like any other coding task:</p> <ul> <li>We analyze the problem,</li> <li>Break it down into smaller parts,</li> <li>Examine each coordinate and its mathematical breakdown,</li> <li>And then put it all together.</li> </ul> <p>I hope that this article has given you a starting point into the wonderful world of coded images and that it gives you the motivation to delve deeper into the specs and try drawing some yourself.</p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://www.webmastersgallery.com/category/uncategorized/" rel="category tag">Others</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div id="pagenavi"> <span class="newer"><a href="http://www.webmastersgallery.com/2024/09/0/" >Newer Entries</a></span> <span class="older"><a href="http://www.webmastersgallery.com/2024/09/0/page/3/" >Older Entries</a></span> <div class="fixed"></div> </div> </div> <!-- main END --> <!-- sidebar START --> <div id="sidebar"> <!-- sidebar north START --> <div id="northsidebar" class="sidebar"> <!-- feeds --> <div class="widget widget_feeds"> <div class="content"> <div id="subscribe"> <a rel="external nofollow" id="feedrss" title="Subscribe to this blog..." href="http://www.webmastersgallery.com/feed/"><abbr title="Really Simple Syndication">RSS</abbr></a> </div> <div class="fixed"></div> </div> </div> <!-- showcase --> <div id="text-389627311" class="widget widget_text"> <div class="textwidget"><a href="http://feeds2.feedburner.com/webmastersgallery" title="Subscribe to my feed" rel="alternate" type="application/rss+xml"><img src="http://www.feedburner.com/fb/images/pub/feed-icon32x32.png" alt="" style="border:0"/></a><a href="http://feeds2.feedburner.com/webmastersgallery" title="Subscribe to my feed" rel="alternate" type="application/rss+xml">Subscribe for latest Updates</a></div> </div><div id="text-389629461" class="widget widget_text"> <div class="textwidget"><form style="border:1px solid #ccc;padding:3px;text-align:center;" action="http://feedburner.google.com/fb/a/mailverify" method="post" target="popupwindow" onsubmit="window.open('http://feedburner.google.com/fb/a/mailverify?uri=webmastersgallery', 'popupwindow', 'scrollbars=yes,width=550,height=520');return true"><p>Enter your email address:</p><p><input type="text" style="width:140px" name="email"/></p><input type="hidden" value="webmastersgallery" name="uri"/><input type="hidden" name="loc" value="en_US"/><input type="submit" value="Subscribe" /><p>Delivered by <a href="http://feedburner.google.com" target="_blank" rel="noopener">FeedBurner</a></p></form></div> </div></div> <!-- sidebar north END --> <div id="centersidebar"> <!-- sidebar east START --> <div id="eastsidebar" class="sidebar"> <!-- categories --> <div class="widget widget_categories"> <h3>Categories</h3> <ul> <li class="cat-item cat-item-518"><a href="http://www.webmastersgallery.com/category/affiliate-programs/">Affiliate Programs</a> </li> <li class="cat-item cat-item-147"><a href="http://www.webmastersgallery.com/category/design/">Designing</a> </li> <li class="cat-item cat-item-519"><a href="http://www.webmastersgallery.com/category/domain-names/">Domain Names</a> </li> <li class="cat-item cat-item-37"><a href="http://www.webmastersgallery.com/category/e-commerce/">E-commerce</a> </li> <li class="cat-item cat-item-509"><a href="http://www.webmastersgallery.com/category/internet-directories/">Internet Directories</a> </li> <li class="cat-item cat-item-510"><a href="http://www.webmastersgallery.com/category/message-boards/">Message Boards</a> </li> <li class="cat-item cat-item-1"><a href="http://www.webmastersgallery.com/category/uncategorized/">Others</a> </li> <li class="cat-item cat-item-506"><a href="http://www.webmastersgallery.com/category/programming/">Programming</a> </li> <li class="cat-item cat-item-511"><a href="http://www.webmastersgallery.com/category/promotion-and-marketing/">Promotion and Marketing</a> </li> <li class="cat-item cat-item-534"><a href="http://www.webmastersgallery.com/category/scripts-and-programming/">Scripts and Programming</a> </li> <li class="cat-item cat-item-513"><a href="http://www.webmastersgallery.com/category/search-engines/">Search Engines</a> </li> <li class="cat-item cat-item-135"><a href="http://www.webmastersgallery.com/category/social-media/">Social Media</a> </li> <li class="cat-item cat-item-514"><a href="http://www.webmastersgallery.com/category/softwares/">Softwares</a> </li> <li class="cat-item cat-item-515"><a href="http://www.webmastersgallery.com/category/tips-and-tutorials/">Tips and Tutorials</a> </li> <li class="cat-item cat-item-338"><a href="http://www.webmastersgallery.com/category/web-hosting/">Web Hosting</a> </li> <li class="cat-item cat-item-516"><a href="http://www.webmastersgallery.com/category/webmaster-tools/">Webmaster Tools</a> </li> <li class="cat-item cat-item-501"><a href="http://www.webmastersgallery.com/category/webmaster-resources/">Webmasters Resources</a> </li> <li class="cat-item cat-item-3"><a href="http://www.webmastersgallery.com/category/web-design/">Website Design</a> </li> </ul> </div> </div> <!-- sidebar east END --> <!-- sidebar west START --> <div id="westsidebar" class="sidebar"> <!-- blogroll --> <div class="widget widget_links"> <h3>Blogroll</h3> <ul> <li><a href="http://wordpress.org/development/">Development Blog</a></li> <li><a href="http://codex.wordpress.org/">Documentation</a></li> <li><a href="http://wordpress.org/extend/plugins/">Plugins</a></li> <li><a href="http://wordpress.org/extend/ideas/">Suggest Ideas</a></li> <li><a href="http://wordpress.org/support/">Support Forum</a></li> <li><a href="http://wordpress.org/extend/themes/">Themes</a></li> <li><a href="http://planet.wordpress.org/">WordPress Planet</a></li> </ul> </div> </div> <!-- sidebar west END --> <div class="fixed"></div> </div> <!-- sidebar south START --> <div id="southsidebar" class="sidebar"> <!-- archives --> <div class="widget"> <h3>Archives</h3> <ul> <li><a href='http://www.webmastersgallery.com/2024/11/'>November 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/10/'>October 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/09/' aria-current="page">September 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/08/'>August 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/07/'>July 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/06/'>June 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/05/'>May 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/04/'>April 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/03/'>March 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/02/'>February 2024</a></li> <li><a href='http://www.webmastersgallery.com/2024/01/'>January 2024</a></li> <li><a href='http://www.webmastersgallery.com/2023/12/'>December 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/11/'>November 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/10/'>October 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/09/'>September 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/08/'>August 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/07/'>July 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/06/'>June 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/05/'>May 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/04/'>April 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/03/'>March 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/02/'>February 2023</a></li> <li><a href='http://www.webmastersgallery.com/2023/01/'>January 2023</a></li> <li><a href='http://www.webmastersgallery.com/2022/12/'>December 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/11/'>November 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/10/'>October 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/09/'>September 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/08/'>August 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/07/'>July 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/06/'>June 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/05/'>May 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/04/'>April 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/03/'>March 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/02/'>February 2022</a></li> <li><a href='http://www.webmastersgallery.com/2022/01/'>January 2022</a></li> <li><a href='http://www.webmastersgallery.com/2021/12/'>December 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/11/'>November 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/10/'>October 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/09/'>September 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/08/'>August 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/07/'>July 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/06/'>June 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/05/'>May 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/04/'>April 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/03/'>March 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/02/'>February 2021</a></li> <li><a href='http://www.webmastersgallery.com/2021/01/'>January 2021</a></li> <li><a href='http://www.webmastersgallery.com/2020/12/'>December 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/11/'>November 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/10/'>October 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/09/'>September 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/08/'>August 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/07/'>July 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/06/'>June 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/05/'>May 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/04/'>April 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/03/'>March 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/02/'>February 2020</a></li> <li><a href='http://www.webmastersgallery.com/2020/01/'>January 2020</a></li> <li><a href='http://www.webmastersgallery.com/2019/12/'>December 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/11/'>November 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/10/'>October 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/09/'>September 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/08/'>August 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/07/'>July 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/06/'>June 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/05/'>May 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/04/'>April 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/03/'>March 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/02/'>February 2019</a></li> <li><a href='http://www.webmastersgallery.com/2019/01/'>January 2019</a></li> <li><a href='http://www.webmastersgallery.com/2018/12/'>December 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/11/'>November 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/10/'>October 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/09/'>September 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/08/'>August 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/07/'>July 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/04/'>April 2018</a></li> <li><a href='http://www.webmastersgallery.com/2018/01/'>January 2018</a></li> <li><a href='http://www.webmastersgallery.com/2017/12/'>December 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/11/'>November 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/09/'>September 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/08/'>August 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/07/'>July 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/06/'>June 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/05/'>May 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/04/'>April 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/03/'>March 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/02/'>February 2017</a></li> <li><a href='http://www.webmastersgallery.com/2017/01/'>January 2017</a></li> <li><a href='http://www.webmastersgallery.com/2016/12/'>December 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/11/'>November 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/10/'>October 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/09/'>September 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/08/'>August 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/07/'>July 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/06/'>June 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/05/'>May 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/04/'>April 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/03/'>March 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/02/'>February 2016</a></li> <li><a href='http://www.webmastersgallery.com/2016/01/'>January 2016</a></li> <li><a href='http://www.webmastersgallery.com/2015/12/'>December 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/11/'>November 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/10/'>October 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/09/'>September 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/08/'>August 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/07/'>July 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/06/'>June 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/05/'>May 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/04/'>April 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/03/'>March 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/02/'>February 2015</a></li> <li><a href='http://www.webmastersgallery.com/2015/01/'>January 2015</a></li> <li><a href='http://www.webmastersgallery.com/2014/12/'>December 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/11/'>November 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/10/'>October 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/09/'>September 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/08/'>August 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/07/'>July 2014</a></li> <li><a href='http://www.webmastersgallery.com/2014/06/'>June 2014</a></li> <li><a href='http://www.webmastersgallery.com/2013/07/'>July 2013</a></li> <li><a href='http://www.webmastersgallery.com/2013/01/'>January 2013</a></li> <li><a href='http://www.webmastersgallery.com/2012/12/'>December 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/08/'>August 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/07/'>July 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/06/'>June 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/05/'>May 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/04/'>April 2012</a></li> <li><a href='http://www.webmastersgallery.com/2012/01/'>January 2012</a></li> <li><a href='http://www.webmastersgallery.com/2011/11/'>November 2011</a></li> <li><a href='http://www.webmastersgallery.com/2011/06/'>June 2011</a></li> <li><a href='http://www.webmastersgallery.com/2011/03/'>March 2011</a></li> <li><a href='http://www.webmastersgallery.com/2011/02/'>February 2011</a></li> <li><a href='http://www.webmastersgallery.com/2011/01/'>January 2011</a></li> <li><a href='http://www.webmastersgallery.com/2010/12/'>December 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/11/'>November 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/09/'>September 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/07/'>July 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/06/'>June 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/05/'>May 2010</a></li> <li><a href='http://www.webmastersgallery.com/2010/02/'>February 2010</a></li> <li><a href='http://www.webmastersgallery.com/2009/12/'>December 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/08/'>August 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/07/'>July 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/06/'>June 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/05/'>May 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/04/'>April 2009</a></li> <li><a href='http://www.webmastersgallery.com/2009/03/'>March 2009</a></li> </ul> </div> <!-- meta --> <div class="widget"> <h3>Meta</h3> <ul> <li><a href="http://www.webmastersgallery.com/wp-login.php">Log in</a></li> </ul> </div> </div> <!-- sidebar south END --> </div> <!-- sidebar END --> <div class="fixed"></div> </div> <!-- content END --> <!-- footer START --> <div id="footer"> <a id="gotop" href="#" onclick="MGJS.goTop();return false;">Top</a> <a id="powered" href="http://wordpress.org/">WordPress</a> <div id="copyright"> Copyright © 2009-2024 Webmasters Gallery </div> <div id="themeinfo"> Theme by <a href="http://www.neoease.com/">NeoEase</a>. Valid <a href="http://validator.w3.org/check?uri=referer">XHTML 1.1</a> and <a href="http://jigsaw.w3.org/css-validator/check/referer?profile=css3">CSS 3</a>. </div> </div> <!-- footer END --> </div> <!-- container END --> </div> <!-- wrap END --> </body> </html>