<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Shiyu</title>
        <link>https://shiyui.vercel.app</link>
        <description>My internet hideout, where you can explore topics I am learning, projects I am building, tech blog posts, and know more about who I am...</description>
        <lastBuildDate>Sat, 28 Mar 2026 17:57:27 GMT</lastBuildDate>
        <docs>http://github.com/chanshiyucx/zero</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Shiyu</title>
            <url>https://shiyui.vercel.app/icon.svg</url>
            <link>https://shiyui.vercel.app</link>
        </image>
        <copyright>All rights reserved 2026, Chanshiyu.</copyright>
        <item>
            <title><![CDATA[Hot Pilates]]></title>
            <link>https://shiyui.vercel.app/musing/hot-pilates</link>
            <guid isPermaLink="false">hot-pilates</guid>
            <pubDate>Sat, 28 Mar 2026 17:57:27 GMT</pubDate>
            <content:encoded><![CDATA[<p>Led by Yun, it was my first time trying Hot Pilates. I spent an hour in a 38-degree room, and it completely wiped me out. Maybe my body will keep burning calories for the next three days.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Life</category>
        </item>
        <item>
            <title><![CDATA[Just Right]]></title>
            <link>https://shiyui.vercel.app/musing/just-right</link>
            <guid isPermaLink="false">just-right</guid>
            <pubDate>Mon, 23 Mar 2026 16:27:01 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="1"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/Daily/202603221603.jpeg&#x26;size=large" alt="Theresienwiese" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/Daily/202603221603.jpeg" width="300" height="200"></div>
<p>Took a walk on <a href="https://google.com/maps/place/Theresienwiese,+M%C3%BCnchen" rel="nofollow noopener noreferrer" target="_blank">Theresienwiese</a> on Sunday afternoon. The sun was just right, warm and comfortable. We played and laughed. Moments like this might not come around again.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Life</category>
        </item>
        <item>
            <title><![CDATA[Sudden Snowfall]]></title>
            <link>https://shiyui.vercel.app/musing/sudden-snowfall</link>
            <guid isPermaLink="false">sudden-snowfall</guid>
            <pubDate>Tue, 17 Mar 2026 10:01:37 GMT</pubDate>
            <content:encoded><![CDATA[<p>It was raining heavily when I left work. But by the time I got off the S-Bahn, it had turned into heavy snow. It was so surprising to see that at the start of spring. I rushed home through the snow, my head covered in white.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Life</category>
        </item>
        <item>
            <title><![CDATA[Rediscovered Photos]]></title>
            <link>https://shiyui.vercel.app/musing/rediscovered-photos</link>
            <guid isPermaLink="false">rediscovered-photos</guid>
            <pubDate>Sun, 15 Mar 2026 17:18:01 GMT</pubDate>
            <content:encoded><![CDATA[<p>Found all our trip photos from Paris and Parma two years ago on Yun's portable hard drive. What a nice surprise. The photos I had saved were accidentally deleted at some point.</p>
<p>Looking back, I was still very new to photography and took a lot of bad shots. I spent the whole morning going through them and picking out some decent ones to add to the album <a href="/album#france-paris">france-paris</a>.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Photography</category>
        </item>
        <item>
            <title><![CDATA[Figma Newbie]]></title>
            <link>https://shiyui.vercel.app/musing/figma-newbie</link>
            <guid isPermaLink="false">figma-newbie</guid>
            <pubDate>Sat, 14 Mar 2026 20:19:53 GMT</pubDate>
            <content:encoded><![CDATA[<p>I had a sudden idea to add a screenshot of my blog's home page, just to make the <a href="https://github.com/chanshiyucx/zero" rel="nofollow noopener noreferrer" target="_blank">zero</a> theme more obvious for visitors. So I opened Figma right away. I'm a total newbie with Figma and had only used it once before to design my blog's favicon. You can see it in the browser tab.</p>
<p>I told Grok what I needed and asked it to walk me through the steps, but it didn't work out. The steps it gave were just too complicated for me to follow. So I switched to Gemini. It gave me something simpler and more doable. When I finally got the finished <a href="https://raw.githubusercontent.com/chanshiyucx/zero/refs/heads/master/doc/screenshot/theme.png" rel="nofollow noopener noreferrer" target="_blank">image</a>, I felt a real sense of accomplishment. I think I'm starting to like Figma.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Blog</category>
        </item>
        <item>
            <title><![CDATA[Mittenwald Hiking]]></title>
            <link>https://shiyui.vercel.app/musing/mittenwald-hiking</link>
            <guid isPermaLink="false">mittenwald-hiking</guid>
            <pubDate>Mon, 09 Mar 2026 16:50:30 GMT</pubDate>
            <content:encoded><![CDATA[<p>This was our second time hiking in Mittenwald. Whenever Yuyun and I feel burned out and low on energy, we go hiking to recharge. This time we picked a route that was about 5 hours long. Mittenwald is a small village at the foot of the mountains. The first part of the route was new to us, but the second part we had done before.</p>
<p>I have to say, it was not an easy route. We ended up taking over 6 hours in the end! But it was totally worth it. The scenery along the way was beautiful, through mountains and forests, past lakes and rivers.</p>
<p>By the time we got home, both of us had sore feet. But I'm sure we'll be feeling energetic and full of energy again tomorrow.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Hiking</category>
        </item>
        <item>
            <title><![CDATA[Germany Mittenwald]]></title>
            <link>https://shiyui.vercel.app/album/germany-mittenwald</link>
            <guid isPermaLink="false">germany-mittenwald</guid>
            <pubDate>Sun, 08 Mar 2026 09:37:01 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="6"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260307-Mittenwald/DSC07119.jpeg&#x26;size=large" alt="Mittenwald" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260307-Mittenwald/DSC07119.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260307-Mittenwald/DSC07015.jpeg&#x26;size=large" alt="Lonely Shed" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260307-Mittenwald/DSC07015.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260307-Mittenwald/DSC07038.jpeg&#x26;size=large" alt="Silent Slope" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260307-Mittenwald/DSC07038.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260307-Mittenwald/DSC07069.jpeg&#x26;size=large" alt="Ice to Peak" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260307-Mittenwald/DSC07069.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260307-Mittenwald/DSC07098.jpeg&#x26;size=large" alt="Golden Path" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260307-Mittenwald/DSC07098.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260307-Mittenwald/DSC07107.jpeg&#x26;size=large" alt="Peak Crossing" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260307-Mittenwald/DSC07107.jpeg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Meeting]]></title>
            <link>https://shiyui.vercel.app/musing/meeting</link>
            <guid isPermaLink="false">meeting</guid>
            <pubDate>Fri, 06 Mar 2026 14:00:09 GMT</pubDate>
            <content:encoded><![CDATA[<p>I really don't like meetings. They're always boring and make me sleepy. Yesterday afternoon I had a company meeting that I thought would only be one or two hours. But it ended up going four hours. I was pretty tired and sleepy in the second half.</p>
<p>My English isn't great, especially when it comes to listening and speaking. The speaker was talking fast and using some complicated words, so I was pretty much burned out by the end. I really can't stand meetings, no matter when or where.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Work</category>
        </item>
        <item>
            <title><![CDATA[Photography]]></title>
            <link>https://shiyui.vercel.app/musing/photography</link>
            <guid isPermaLink="false">photography</guid>
            <pubDate>Wed, 04 Mar 2026 16:19:43 GMT</pubDate>
            <content:encoded><![CDATA[<p>As you can see in my blog bio, I call myself a "Budding Photographer." I have a Sony A6700 that I spent a lot of money on two years ago. Since then, I've been trying to learn how to take better photos, with the goal of becoming a decent amateur photographer.</p>
<p>My girlfriend always complains that my photos are pretty bad, especially the ones of her. Though maybe landscape photos are a bit better. I also know I'm not naturally talented at photography, and maybe a phone camera is just a better fit for me, simpler and more convenient.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Photography</category>
        </item>
        <item>
            <title><![CDATA[The Wolf of Wall Street]]></title>
            <link>https://shiyui.vercel.app/musing/the-wolf-of-wall-street</link>
            <guid isPermaLink="false">the-wolf-of-wall-street</guid>
            <pubDate>Mon, 02 Mar 2026 14:56:45 GMT</pubDate>
            <content:encoded><![CDATA[<p>My girlfriend and I watched <em>The Wolf of Wall Street</em> at home last weekend. We originally hoped to learn something about finance, but it turned out to be more of a comedy, haha. The subtitles were actually hilarious. We didn't learn anything about investing, just a few dirty words.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Movie</category>
        </item>
        <item>
            <title><![CDATA[German Learning]]></title>
            <link>https://shiyui.vercel.app/musing/german-learning</link>
            <guid isPermaLink="false">german-learning</guid>
            <pubDate>Sun, 01 Mar 2026 20:29:36 GMT</pubDate>
            <content:encoded><![CDATA[<p>It's been a long time since I last studied German. After my German course ended in July last year, I haven't touched it since. There are a few reasons for that. The main one is that I just lost motivation. I originally started learning German to find a job, but I figured it would be really hard to land a job through German, and that English would be the easier route.</p>
<p>In the end, it turned out just like I thought. I found my job through English, not German. Will I keep learning German someday? Honestly, I have no idea.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/German</category>
        </item>
        <item>
            <title><![CDATA[Germany Schliersee]]></title>
            <link>https://shiyui.vercel.app/album/germany-schliersee</link>
            <guid isPermaLink="false">germany-schliersee</guid>
            <pubDate>Sat, 28 Feb 2026 18:39:15 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="3"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260228-Schliersee/DSC06953.jpeg&#x26;size=large" alt="Lakeside Village" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260228-Schliersee/DSC06953.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260228-Schliersee/DSC06977.jpeg&#x26;size=large" alt="Snowy Mountains" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260228-Schliersee/DSC06977.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260228-Schliersee/DSC06968.jpeg&#x26;size=large" alt="Wild Ducks" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260228-Schliersee/DSC06968.jpeg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[About Musing Page]]></title>
            <link>https://shiyui.vercel.app/musing/about-musing-page</link>
            <guid isPermaLink="false">about-musing-page</guid>
            <pubDate>Fri, 27 Feb 2026 14:38:22 GMT</pubDate>
            <content:encoded><![CDATA[<p>When I added the Musing Page to my blog, I was looking forward to using it as a way to improve my English. Honestly, my English is pretty terrible — especially speaking. I can barely get a sentence out when talking to people, and without AI, I can't write anything on my own.</p>
<p>I've always been frustrated by this, even though I've put a lot of time and effort into trying to get better. Nothing really seemed to work. This time, I'm trying something different: writing short daily musings. I hope I can keep it up.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/English</category>
        </item>
        <item>
            <title><![CDATA[Sunshine in Barcelona]]></title>
            <link>https://shiyui.vercel.app/journal/sunshine-in-barcelona</link>
            <guid isPermaLink="false">sunshine-in-barcelona</guid>
            <pubDate>Thu, 26 Feb 2026 11:52:57 GMT</pubDate>
            <content:encoded><![CDATA[<p>Our first trip of the new year. We were lucky to leave freezing Munich just before the snow arrived and step straight into warm sunshine in Barcelona. Five days, but plenty to remember.</p>
<h2 id="sagrada-família"><a href="#sagrada-família">Sagrada Família</a></h2>
<p>Sagrada Família barely needs an introduction. Gaudí's life work, still pulling in crowds from every corner of the world. We happened to be there on the day it was officially topped out. 144 years in the making, now the tallest church in the world. The mayor and the archbishop both spoke in the square outside, the crowd was enormous, and the whole thing felt genuinely historic. Just got lucky.</p>
<p>From Park Güell up on the hill, we could see the whole city spread out below. Sagrada Família rose above everything else, unmistakable against the low rooftops.</p>
<div class="photo-albums" data-total="2"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06713.jpeg&#x26;size=large" alt="Barcelona" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06713.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06732.jpeg&#x26;size=large" alt="Sagrada Familia" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06732.jpeg" width="300" height="200"></div>
<h2 id="casa-batlló--casa-milà"><a href="#casa-batlló--casa-milà">Casa Batlló &#x26; Casa Milà</a></h2>
<p>Casa Batlló and Casa Milà both are essential Gaudí, the kind of thing you feel obligated to see in Barcelona.</p>
<p>I'll be honest, I'm not really an architecture person, but Casa Batlló's facade stopped me in my tracks. The mosaic tiles are intricate and colorful, almost alive in the sunlight. Casa Milà feels grander. Different vibes, both worth a visit.</p>
<div class="photo-albums" data-total="2"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06602.jpeg&#x26;size=large" alt="Casa Batlló" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06602.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06671.jpeg&#x26;size=large" alt="Casa Milà" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06671.jpeg" width="300" height="200"></div>
<h2 id="street-walking"><a href="#street-walking">Street Walking</a></h2>
<p>Some of the best moments on any trip are when you're not really doing anything, just wandering. Barcelona's winter sun is generous, and walking around without a plan felt cozy.</p>
<p>The pigeons at Plaça de Catalunya were something else. Hundreds of them, completely unbothered by people, waddling around like they owned the place. I'd never seen that many birds packed into one square.</p>
<div class="photo-albums" data-total="2"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06722.jpeg&#x26;size=large" alt="The Winged Solo" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06722.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06638.jpeg&#x26;size=large" alt="Plaça de Catalunya" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06638.jpeg" width="300" height="200"></div>
<p>We'd just gotten home and I was already looking at flights again. Maybe March, when everything starts waking up. Feels like a good time to go somewhere.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Journal/Travel</category>
        </item>
        <item>
            <title><![CDATA[Spain Barcelona]]></title>
            <link>https://shiyui.vercel.app/album/spain-barcelona</link>
            <guid isPermaLink="false">spain-barcelona</guid>
            <pubDate>Wed, 25 Feb 2026 14:32:07 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="9"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06713.jpeg&#x26;size=large" alt="Barcelona" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06713.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06732.jpeg&#x26;size=large" alt="Sagrada Familia" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06732.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06858.jpeg&#x26;size=large" alt="Façana de la Passió" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06858.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06844.jpeg&#x26;size=large" alt="Liquid Light" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06844.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06602.jpeg&#x26;size=large" alt="Casa Batlló" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06602.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06671.jpeg&#x26;size=large" alt="Casa Milà" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06671.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06657.jpeg&#x26;size=large" alt="La Seu" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06657.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06898.jpeg&#x26;size=large" alt="Celestial Peak" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06898.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20260220-Barcelona/DSC06722.jpeg&#x26;size=large" alt="The Winged Solo" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20260220-Barcelona/DSC06722.jpeg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Before Vacation]]></title>
            <link>https://shiyui.vercel.app/musing/before-vacation</link>
            <guid isPermaLink="false">before-vacation</guid>
            <pubDate>Tue, 10 Feb 2026 15:21:49 GMT</pubDate>
            <content:encoded><![CDATA[<p>Chinese New Year is next week. Most of my colleagues have already gone back home, there's only three of us left in the office now. It's pretty quiet around here.</p>
<p>This will be my third year in Munich instead of going home. I'm planning to go to Barcelona this year, but I haven't booked my flight or put in for time off yet. Honestly though, I can't focus on work at all right now. I can't wait.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Life</category>
        </item>
        <item>
            <title><![CDATA[UI UX Design]]></title>
            <link>https://shiyui.vercel.app/musing/ui-ux-design</link>
            <guid isPermaLink="false">ui-ux-design</guid>
            <pubDate>Sun, 08 Feb 2026 20:56:30 GMT</pubDate>
            <content:encoded><![CDATA[<p>In UI/UX design, finding the right balance between functionality and visual appeal is always tricky.</p>
<p>I spent two full days going back and forth on whether to add a staggered fade-in animation to my blog pages, tweaking all sorts of parameters and details, but I still couldn't get it quite right. It's always tough deciding between practicality and aesthetics.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Blog</category>
        </item>
        <item>
            <title><![CDATA[Blog Bio]]></title>
            <link>https://shiyui.vercel.app/musing/blog-bio</link>
            <guid isPermaLink="false">blog-bio</guid>
            <pubDate>Sat, 07 Feb 2026 21:23:05 GMT</pubDate>
            <content:encoded><![CDATA[<p>"<em>Seize the day, gather ye rosebuds while ye may.</em>" I really like this quote. There's something poetic about it that captures a meaningful philosophy of life, so I decided to put it in my blog bio.</p>
<p>I looked it up and found it's from the poem <em>To the Virgins, to Make Much of Time</em>.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Musing/Blog</category>
        </item>
        <item>
            <title><![CDATA[Germany NeuschwansteinCastle]]></title>
            <link>https://shiyui.vercel.app/album/germany-neuschwansteincastle</link>
            <guid isPermaLink="false">germany-neuschwansteincastle</guid>
            <pubDate>Tue, 06 Jan 2026 16:55:57 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="6"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251231-NeuschwansteinCastle/DSC06463.jpeg&#x26;size=large" alt="White World" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251231-NeuschwansteinCastle/DSC06463.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251231-NeuschwansteinCastle/DSC06502.jpeg&#x26;size=large" alt="NeuschwansteinCastle" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251231-NeuschwansteinCastle/DSC06502.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251231-NeuschwansteinCastle/DSC06440.jpeg&#x26;size=large" alt="Spires" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251231-NeuschwansteinCastle/DSC06440.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251231-NeuschwansteinCastle/DSC06467.jpeg&#x26;size=large" alt="Handsome Horse" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251231-NeuschwansteinCastle/DSC06467.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251231-NeuschwansteinCastle/DSC06533.jpeg&#x26;size=large" alt="Snowy Tree" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251231-NeuschwansteinCastle/DSC06533.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251231-NeuschwansteinCastle/DSC06513.jpeg&#x26;size=large" alt="Rime" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251231-NeuschwansteinCastle/DSC06513.jpeg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Austria Kufstein]]></title>
            <link>https://shiyui.vercel.app/album/austria-kufstein</link>
            <guid isPermaLink="false">austria-kufstein</guid>
            <pubDate>Mon, 10 Nov 2025 14:51:07 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="3"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251025-Kufstein/DSC06091.jpeg&#x26;size=large" alt="Crimson Peaks" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251025-Kufstein/DSC06091.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251025-Kufstein/DSC06301.jpeg&#x26;size=large" alt="Autumn Reflections" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251025-Kufstein/DSC06301.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20251025-Kufstein/DSC06392.jpeg&#x26;size=large" alt="Tiny Castle" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20251025-Kufstein/DSC06392.jpeg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[A Weekend Spent Chasing Mushrooms]]></title>
            <link>https://shiyui.vercel.app/journal/a-weekend-spent-chasing-mushrooms</link>
            <guid isPermaLink="false">a-weekend-spent-chasing-mushrooms</guid>
            <pubDate>Mon, 22 Sep 2025 16:48:18 GMT</pubDate>
            <content:encoded><![CDATA[<p>On Saturday, my girlfriend and I joined a mushroom-picking trip—it was my first time experiencing Munich's mushroom season. We'd packed everything the night before: paper bags, cloth bags, gloves, and a small knife.</p>
<p>It took us about an hour to get there, and then we arrived at our meeting point, Neupullach Gasthaus. Our group of about a dozen included mostly Chinese students, along with a few older ladies.</p>
<p>After a short wait, our guide showed up—everyone affectionately calls him "Brother Fang." He led us straight into the forest. It was peak mushroom season, and the forest floor was dotted with all kinds of mushrooms—some delicious, others definitely not for eating. Since many in the group were beginners, Brother Fang walked us through the basics on how to tell edible species from the toxic ones.</p>
<p>Even though it was my first real trip, my girlfriend had already taught me a few basics on our hikes, so I could recognize some common edible types. Instead of sticking close to the group, we searched the forest on our own.</p>
<p>After about an hour and a half, crossing through two patches of forest, our haul was pretty decent—we managed to fill up a big bag. To catch the return bus, we decided to wrap up and head home.</p>
<div class="photo-albums" data-total="3"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250920-Munich-Mushroom/Mushroom.jpg&#x26;size=large" alt="Suillus granulatus" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250920-Munich-Mushroom/Mushroom.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250920-Munich-Mushroom/IMG_3377.jpg&#x26;size=large" alt="Rotbrauner Scheidenstreifling" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250920-Munich-Mushroom/IMG_3377.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250920-Munich-Mushroom/IMG_3382.jpg&#x26;size=large" alt="Today&#x27;s Takeaways" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250920-Munich-Mushroom/IMG_3382.jpg" width="300" height="200"></div>
<p>Once home, we went through what we'd picked: mostly <em>Bay bolete</em> and <em>Red-footed bolete</em>, plus a few <em>Chanterelles</em>. That night we cooked up a feast—two stir-fries that turned out delicious. We didn't find the porcini or honey mushrooms we'd hoped for, but with honey mushroom season coming up, we're already excited to try again.</p>
<p>Actually, next weekend's trip is already planned. After this week's rain, the forest should be bursting with mushrooms—can't wait to get back out there.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Journal/Daily</category>
        </item>
        <item>
            <title><![CDATA[Germany Munich]]></title>
            <link>https://shiyui.vercel.app/album/germany-munich</link>
            <guid isPermaLink="false">germany-munich</guid>
            <pubDate>Wed, 27 Aug 2025 19:21:48 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="3"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250826-Munich/IMG_3275.jpg&#x26;size=large" alt="Neues Rathaus" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250826-Munich/IMG_3275.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250826-Munich/IMG_3282.jpg&#x26;size=large" alt="Zwiebeltürme" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250826-Munich/IMG_3282.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250826-Munich/IMG_3294.jpg&#x26;size=large" alt="Frauenkirche" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250826-Munich/IMG_3294.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Naples Chaos Charm Perfect Pizza]]></title>
            <link>https://shiyui.vercel.app/journal/naples-chaos-charm-perfect-pizza</link>
            <guid isPermaLink="false">naples-chaos-charm-perfect-pizza</guid>
            <pubDate>Tue, 12 Aug 2025 21:50:57 GMT</pubDate>
            <content:encoded><![CDATA[<p>When my girlfriend and I arrived in Naples from Munich in late July, the first thing we noticed was the heat. The Mediterranean sun was intense, but we were happy with the weather. We had five days of clear blue skies ahead of us in one of Italy's most authentic cities.</p>
<h2 id="pizza-the-real-deal"><a href="#pizza-the-real-deal">Pizza, the Real Deal</a></h2>
<p>No trip to Naples would be complete without paying homage to the birthplace of pizza, and L'Antica Pizzeria da Michele was our first stop. The line was already wrapped around the block, and with more than forty thousand reviews online, we figured we were in the right place.</p>
<p>Inside was pretty chaotic but in a good way—dough flying through the air, waiters squeezing between crowded tables, everyone buzzing with excitement. We kept it classic and ordered a Margherita and a Marinara. When the pizzas landed on our table—charred on the edges, loaded with San Marzano tomatoes and fresh mozzarella—I finally got what all the hype was about. And the best part? The pizzas were unbelievably cheap.</p>
<div class="photo-albums" data-total="2"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/IMG_2784.jpg&#x26;size=large" alt="Marinara" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/IMG_2784.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/IMG_2785.jpg&#x26;size=large" alt="Margherita" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/IMG_2785.jpg" width="300" height="200"></div>
<h2 id="underground-wonders"><a href="#underground-wonders">Underground Wonders</a></h2>
<p>The next morning found us exploring Naples' gritty city center. Everyone raves about Toledo Metro Station as "the world's most beautiful subway stop," but honestly, it was nice but not as amazing as everyone says.</p>
<p>The real discovery came at a bustling seafood stand where we devoured fresh mussels and clams, tasted even better with the sea breeze.</p>
<p>Later, we joined a tour of Napoli Sotterranea, the underground city beneath Naples. Walking through those narrow tunnels felt like stepping back in time. To finish the day, we visited Naples Cathedral. The place was dimly lit with candles, locals murmuring prayers around us, and for a moment the chaos of the city outside completely disappeared.</p>
<div class="photo-albums" data-total="2"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/IMG_2842.jpg&#x26;size=large" alt="Street Statue" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/IMG_2842.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05394.jpg&#x26;size=large" alt="Underground City" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05394.jpg" width="300" height="200"></div>
<h2 id="ancient-pompeii"><a href="#ancient-pompeii">Ancient Pompeii</a></h2>
<p>We set aside a full day for Pompeii, and it turned out to be the right call. The train ride itself was worth it—rolling past the countryside with Mount Vesuvius in the distance, both beautiful and a little eerie when you think about what happened here two thousand years ago.</p>
<p>Once inside the ruins, it honestly felt surreal. We wandered through stone streets, peeked into old houses and bathhouses, and tried to picture daily life frozen in time. The site is vast—we spent almost six hours walking around and still felt like we'd only seen a fraction.</p>
<p>The adventure didn't end there, though. A wildfire nearby shut down the train service, so we ended up packed into a replacement bus with a bunch of other sweaty, tired tourists. It was slow and chaotic, but somehow it added to the whole Pompeii experience—like the day wasn't ready to let us go just yet.</p>
<div class="photo-albums" data-total="2"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05534.jpg&#x26;size=large" alt="Wall of Pompeii" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05534.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05542.jpg&#x26;size=large" alt="Vesuvius Framed" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05542.jpg" width="300" height="200"></div>
<h2 id="island-paradise"><a href="#island-paradise">Island Paradise</a></h2>
<p>The ferry ride to Capri took about fifty minutes, and the whole way it felt like we were heading straight into a postcard. The island has a reputation for being glamorous—and expensive—and it didn't take long for us to notice both.</p>
<p>Still, the views made up for the prices. We took a chairlift up to the top of the mountain, from the top, the whole Bay of Naples opened up around us, dotted with yachts that looked tiny from so high up. It was one of those views you just stand and stare at, trying to take it all in.</p>
<p>We had planned to visit the famous Blue Grotto, but the sea was too rough, so it was closed. A little disappointing, sure, but that's travel—sometimes the unexpected ends up being part of the story.</p>
<div class="photo-albums" data-total="2"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05647.jpg&#x26;size=large" alt="Boats and Rocks" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05647.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05784.jpg&#x26;size=large" alt="Blue Grotto" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05784.jpg" width="300" height="200"></div>
<h2 id="a-lemon-scented-goodbye"><a href="#a-lemon-scented-goodbye">A Lemon-Scented Goodbye</a></h2>
<p>On our last day we took a trip to Sorrento, a town perched high on the cliffs with views that go on forever. The place is famous for its lemons, but what I'll remember most is how calm it felt compared to Naples.</p>
<p>We treated ourselves to a big seafood lunch by the water, then found an old bench down by the harbor. For about an hour, we didn't do much of anything—just sat there, watching ferries come and go, while pigeons hovered around hoping for crumbs. It was one of those rare travel moments where you're not rushing anywhere. Just the sea, the sunshine, and the person you love sitting beside you.</p>
<div class="photo-albums" data-total="2"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05815.jpg&#x26;size=large" alt="Limoncello" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05815.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05785.jpg&#x26;size=large" alt="Vesuvius Rising" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05785.jpg" width="300" height="200"></div>
<h2 id="the-real-naples"><a href="#the-real-naples">The Real Naples</a></h2>
<p>I'll be honest—Naples isn't the kind of city that tries to win you over at first sight. The traffic is crazy, the streets can feel rough around the edges, and half the time things don't seem to work the way you expect. But that's also what makes it unforgettable.</p>
<p>In just five days we saw a city where ancient ruins sit next to busy pizzerias, where tunnels run under crowded streets, and where chaos and beauty somehow manage to live side by side. Naples doesn't try to impress you—it just is what it is. And maybe that's why it sticks with you long after you've left.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Journal/Travel</category>
        </item>
        <item>
            <title><![CDATA[Italy Napoli]]></title>
            <link>https://shiyui.vercel.app/album/italy-napoli</link>
            <guid isPermaLink="false">italy-napoli</guid>
            <pubDate>Tue, 05 Aug 2025 16:46:27 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="9"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05640.jpg&#x26;size=large" alt="Capri Bay" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05640.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05647.jpg&#x26;size=large" alt="Boats and Rocks" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05647.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05664.jpg&#x26;size=large" alt="Anacapri" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05664.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05785.jpg&#x26;size=large" alt="Vesuvius Rising" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05785.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05845.jpg&#x26;size=large" alt="Capri Harbor" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05845.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05784.jpg&#x26;size=large" alt="Blue Grotto" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05784.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05534.jpg&#x26;size=large" alt="Wall of Pompeii" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05534.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05542.jpg&#x26;size=large" alt="Vesuvius Framed" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05542.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250729-Napoli/DSC05815.jpg&#x26;size=large" alt="Limoncello" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250729-Napoli/DSC05815.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Germany Partnachklamm]]></title>
            <link>https://shiyui.vercel.app/album/germany-partnachklamm</link>
            <guid isPermaLink="false">germany-partnachklamm</guid>
            <pubDate>Sun, 06 Jul 2025 21:52:41 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="6"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250705-Partnachklamm/DSC05024.jpg&#x26;size=large" alt="Alpine Ascent" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250705-Partnachklamm/DSC05024.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250705-Partnachklamm/DSC05090.jpg&#x26;size=large" alt="Rushing Blue" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250705-Partnachklamm/DSC05090.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250705-Partnachklamm/DSC05165.jpg&#x26;size=large" alt="Stone Madonna" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250705-Partnachklamm/DSC05165.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250705-Partnachklamm/DSC05179.jpg&#x26;size=large" alt="Verdant Rush" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250705-Partnachklamm/DSC05179.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250705-Partnachklamm/DSC05212.jpg&#x26;size=large" alt="Peak of Silence" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250705-Partnachklamm/DSC05212.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250705-Partnachklamm/DSC05303.jpg&#x26;size=large" alt="Distant Majesty" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250705-Partnachklamm/DSC05303.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Germany Eibsee]]></title>
            <link>https://shiyui.vercel.app/album/germany-eibsee</link>
            <guid isPermaLink="false">germany-eibsee</guid>
            <pubDate>Sat, 10 May 2025 19:38:39 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="6"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250510-Eibsee/DSC04861.jpeg&#x26;size=large" alt="Church" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250510-Eibsee/DSC04861.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250510-Eibsee/DSC04866.jpeg&#x26;size=large" alt="Green Path" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250510-Eibsee/DSC04866.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250510-Eibsee/DSC04891.jpeg&#x26;size=large" alt="Lazy Cattle" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250510-Eibsee/DSC04891.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250510-Eibsee/DSC04956.jpeg&#x26;size=large" alt="Zugspitze" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250510-Eibsee/DSC04956.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250510-Eibsee/DSC04979.jpeg&#x26;size=large" alt="Bavaria&#x27;s Crown" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250510-Eibsee/DSC04979.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250510-Eibsee/DSC04992.jpeg&#x26;size=large" alt="Eibsee" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250510-Eibsee/DSC04992.jpeg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Croatia Dubrovnik]]></title>
            <link>https://shiyui.vercel.app/album/croatia-dubrovnik</link>
            <guid isPermaLink="false">croatia-dubrovnik</guid>
            <pubDate>Sun, 20 Apr 2025 10:38:57 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="6"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250407-Dubrovnik/DSC04589.jpg&#x26;size=large" alt="Old Town Walls" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250407-Dubrovnik/DSC04589.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250407-Dubrovnik/DSC04591.jpg&#x26;size=large" alt="Seaside Bastion" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250407-Dubrovnik/DSC04591.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250407-Dubrovnik/DSC04623.jpg&#x26;size=large" alt="Rocky Waters" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250407-Dubrovnik/DSC04623.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250407-Dubrovnik/DSC04721.jpg&#x26;size=large" alt="Red Rooftops" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250407-Dubrovnik/DSC04721.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250407-Dubrovnik/DSC04798.jpg&#x26;size=large" alt="Miniature Kingdom" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250407-Dubrovnik/DSC04798.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20250407-Dubrovnik/DSC04814.jpg&#x26;size=large" alt="Tranquil Blue" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20250407-Dubrovnik/DSC04814.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[5 Reasons Why You Should Start Writing Technical Articles]]></title>
            <link>https://shiyui.vercel.app/journal/5-reasons-why-you-should-start-writing-technical-articles</link>
            <guid isPermaLink="false">5-reasons-why-you-should-start-writing-technical-articles</guid>
            <pubDate>Tue, 14 Jan 2025 23:20:03 GMT</pubDate>
            <content:encoded><![CDATA[<p>Today, I read an insightful article online about the benefits of writing technical articles. It outlined five compelling reasons to start writing.</p>
<h2 id="knowledge-improvement"><a href="#knowledge-improvement">Knowledge improvement</a></h2>
<p>Writing technical articles is an excellent way to deepen your understanding of a subject. As the saying goes, <strong>"You don't truly understand something unless you can explain it to your grandmother."</strong> Writing forces you to explore topics in detail, research thoroughly, and consider different perspectives. This process not only enhances your knowledge but also helps you communicate it more effectively.</p>
<h2 id="better-communication-skills"><a href="#better-communication-skills">Better communication skills</a></h2>
<p>Writing is more than just knowledge, it's about conveying your thoughts clearly and effectively. Good communication skills are vital in all areas of life, and writing articles is a practical way to refine them.</p>
<h2 id="improve-english"><a href="#improve-english">Improve English</a></h2>
<p>If English isn't your first language, writing articles can significantly improve your proficiency. Through consistent practice, you'll expand your vocabulary, refine your grammar, and boost your confidence in both writing and speaking.</p>
<h2 id="public-exposure"><a href="#public-exposure">Public exposure</a></h2>
<p>Publishing articles regularly establishes you as an authority in your field. This can open doors to new opportunities, such as career advancements or collaborations. Additionally, well-written articles earn respect within your professional community, which can help you negotiate better roles or benefits.</p>
<h2 id="financial-opportunities"><a href="#financial-opportunities">Financial opportunities</a></h2>
<p>Writing technical articles can also provide a source of extra income. While it might not replace your primary job, it can still play a meaningful role in your financial stability.</p>
<p>By starting your journey as a technical writer, you'll not only grow personally and professionally but also contribute valuable knowledge to others.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Journal/English</category>
        </item>
        <item>
            <title><![CDATA[Italy Roma]]></title>
            <link>https://shiyui.vercel.app/album/italy-roma</link>
            <guid isPermaLink="false">italy-roma</guid>
            <pubDate>Mon, 06 Jan 2025 16:46:09 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="9"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC02890.jpg&#x26;size=large" alt="Eternal Colosseum" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC02890.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC02915.jpg&#x26;size=large" alt="Arch of Titus" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC02915.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC03013.jpg&#x26;size=large" alt="Roman Legacy" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC03013.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC03238.jpg&#x26;size=large" alt="Golden Vittoriano" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC03238.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC03260.jpg&#x26;size=large" alt="Divine Artistry" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC03260.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC03421.jpg&#x26;size=large" alt="Vatican&#x27;s Crown" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC03421.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC03606.jpg&#x26;size=large" alt="Pantheon&#x27;s Grace" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC03606.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC03808.jpg&#x26;size=large" alt="Castel Sant&#x27;Angelo" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC03808.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241229-Roma/DSC03861.jpg&#x26;size=large" alt="Bernini’s Baldachin" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241229-Roma/DSC03861.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Germany Nymphenburg]]></title>
            <link>https://shiyui.vercel.app/album/germany-nymphenburg</link>
            <guid isPermaLink="false">germany-nymphenburg</guid>
            <pubDate>Mon, 09 Dec 2024 22:32:20 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="3"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241208-Nymphenburg/DSC02780.jpg&#x26;size=large" alt="BMW Museum" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241208-Nymphenburg/DSC02780.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241208-Nymphenburg/DSC02825.jpg&#x26;size=large" alt="Nymphenburg Panorama" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241208-Nymphenburg/DSC02825.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241208-Nymphenburg/DSC02847.jpg&#x26;size=large" alt="GuGu" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241208-Nymphenburg/DSC02847.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Germany Brauneck]]></title>
            <link>https://shiyui.vercel.app/album/germany-brauneck</link>
            <guid isPermaLink="false">germany-brauneck</guid>
            <pubDate>Sun, 01 Dec 2024 21:22:26 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="6"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241201-Brauneck/DSC02519.jpg&#x26;size=large" alt="Snowy Mountain Village" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241201-Brauneck/DSC02519.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241201-Brauneck/DSC02549.jpg&#x26;size=large" alt="Chasing the Sky" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241201-Brauneck/DSC02549.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241201-Brauneck/DSC02603.jpg&#x26;size=large" alt="Horizon of the Plains" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241201-Brauneck/DSC02603.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241201-Brauneck/DSC02611.jpg&#x26;size=large" alt="The Snowy Alps" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241201-Brauneck/DSC02611.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241201-Brauneck/DSC02670.jpg&#x26;size=large" alt="Serene Valley Lake" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241201-Brauneck/DSC02670.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241201-Brauneck/DSC02703.jpg&#x26;size=large" alt="Skyline Beyond" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241201-Brauneck/DSC02703.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Germany Barmsee]]></title>
            <link>https://shiyui.vercel.app/album/germany-barmsee</link>
            <guid isPermaLink="false">germany-barmsee</guid>
            <pubDate>Sat, 16 Nov 2024 19:10:34 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="6"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241116-Barmsee/DSC02373.jpg&#x26;size=large" alt="Lakeside Cabin" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241116-Barmsee/DSC02373.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241116-Barmsee/DSC02393.jpg&#x26;size=large" alt="Shoreline Harmony" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241116-Barmsee/DSC02393.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241116-Barmsee/DSC02403.jpg&#x26;size=large" alt="Serene Lake" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241116-Barmsee/DSC02403.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241116-Barmsee/DSC02409.jpg&#x26;size=large" alt="Smoke by the Lake" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241116-Barmsee/DSC02409.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241116-Barmsee/DSC02429.jpg&#x26;size=large" alt="Lake of Echoing Mountains" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241116-Barmsee/DSC02429.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20241116-Barmsee/DSC02483.jpg&#x26;size=large" alt="Meadowfoot Town" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20241116-Barmsee/DSC02483.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Spain Palma]]></title>
            <link>https://shiyui.vercel.app/album/spain-palma</link>
            <guid isPermaLink="false">spain-palma</guid>
            <pubDate>Tue, 13 Feb 2024 11:18:16 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="6"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240213-Palma/DSC01475.jpeg&#x26;size=large" alt="Twilight Serene" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240213-Palma/DSC01475.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240213-Palma/IMG_7909.jpeg&#x26;size=large" alt="月が綺麗ですね" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240213-Palma/IMG_7909.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240213-Palma/DSC01533.jpeg&#x26;size=large" alt="Ocean Trio" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240213-Palma/DSC01533.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240213-Palma/DSC01585.jpeg&#x26;size=large" alt="Catedral de Mallorca" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240213-Palma/DSC01585.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240213-Palma/DSC01604.jpeg&#x26;size=large" alt="Azure Sky" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240213-Palma/DSC01604.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240213-Palma/DSC01878.jpeg&#x26;size=large" alt="Passionate Love" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240213-Palma/DSC01878.jpeg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[France Paris]]></title>
            <link>https://shiyui.vercel.app/album/france-paris</link>
            <guid isPermaLink="false">france-paris</guid>
            <pubDate>Fri, 09 Feb 2024 11:00:18 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="9"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC00416.jpeg&#x26;size=large" alt="La Joconde" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC00416.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC00359.jpeg&#x26;size=large" alt="Nike of Samothrace" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC00359.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC00697.jpeg&#x26;size=large" alt="Vénus de Milo" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC00697.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC00363.jpeg&#x26;size=large" alt="Galerie d&#x27;Apollon" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC00363.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC00600.jpeg&#x26;size=large" alt="Palais du Louvre" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC00600.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC01200.jpeg&#x26;size=large" alt="Grande Galerie" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC01200.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC01308.jpeg&#x26;size=large" alt="Jardins du château de Versailles" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC01308.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC01407.jpeg&#x26;size=large" alt="Banks of the Seine" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC01407.jpeg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/20240209-Paris/DSC01465.jpeg&#x26;size=large" alt="Tour Eiffel" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/20240209-Paris/DSC01465.jpeg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Changsha Yuelu]]></title>
            <link>https://shiyui.vercel.app/album/changsha-yuelu</link>
            <guid isPermaLink="false">changsha-yuelu</guid>
            <pubDate>Sun, 28 Jan 2024 12:03:50 GMT</pubDate>
            <content:encoded><![CDATA[<div class="photo-albums" data-total="3"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/Changsha/DSC01999.jpg&#x26;size=large" alt="Aiwan Pavilion" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/Changsha/DSC01999.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/Changsha/DSC01904.jpg&#x26;size=large" alt="Blossoms in Full Bloom" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/Changsha/DSC01904.jpg" width="300" height="200"><img src="https://cx-onedrive.pages.dev/api/thumbnail?path=/Album/Changsha/DSC01960.jpg&#x26;size=large" alt="Skyline Shadows Below" originalsrc="https://cx-onedrive.pages.dev/api/raw?path=/Album/Changsha/DSC01960.jpg" width="300" height="200"></div>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Album</category>
        </item>
        <item>
            <title><![CDATA[Rclone Two Way Sync With OneDrive]]></title>
            <link>https://shiyui.vercel.app/craft/rclone-two-way-sync-with-onedrive</link>
            <guid isPermaLink="false">rclone-two-way-sync-with-onedrive</guid>
            <pubDate>Wed, 25 Mar 2026 11:10:57 GMT</pubDate>
            <content:encoded><![CDATA[<p>I wanted to sync a folder from an external hard drive to OneDrive as a backup. But during setup, I realized it's not as straightforward on macOS as I expected.</p>
<p>The OneDrive client on macOS is limited to a single sync folder, while the folder I want to sync is on an external drive. I tried to use a symlink to mount that folder into the OneDrive directory. However, this approach is not supported on macOS.</p>
<p>With the help of AI, I found Rclone. It can bypass the limitations of the OneDrive client and sync any folder directly to the cloud.</p>
<h2 id="what-bisync-does"><a href="#what-bisync-does">What bisync does</a></h2>
<p>Rclone has a command called <code>bisync</code> that keeps two locations in sync:</p>
<ul>
<li>Add a file locally, it shows up in the cloud</li>
<li>Add a file in the cloud, it shows up locally</li>
<li>Delete a file on either side, it gets deleted on the other</li>
</ul>
<h2 id="install-rclone"><a href="#install-rclone">Install rclone</a></h2>
<figure raw="brew install rclone
" data-language="bash" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="bash" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="bash" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">brew</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> install</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> rclone</span></span></code></pre></figure>
<h2 id="connect-to-onedrive"><a href="#connect-to-onedrive">Connect to OneDrive</a></h2>
<figure raw="rclone config
" data-language="bash" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="bash" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="bash" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">rclone</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> config</span></span></code></pre></figure>
<p>Follow the steps:</p>
<ol>
<li>Type <code>n</code> to create a new config</li>
<li>Name it <code>onedrive</code></li>
<li>Choose <code>Microsoft OneDrive</code></li>
<li>Press enter to skip <code>client_id</code> and <code>secret</code></li>
<li>Choose your account type (personal or business)</li>
<li>Log in in the browser when it opens</li>
<li>Finish setup</li>
</ol>
<p>Check if it works:</p>
<figure raw="rclone lsd onedrive:
" data-language="bash" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="bash" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="bash" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">rclone</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> lsd</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> onedrive:</span></span></code></pre></figure>
<p>If you see your folders, it is ready.</p>
<h2 id="first-time-setup"><a href="#first-time-setup">First-time setup</a></h2>
<p>Before running <code>bisync</code> for the first time, you need to pass <code>--resync</code> to build the initial baseline. This tells rclone to scan both sides and record their current state. Future syncs compare against this snapshot to detect changes.</p>
<blockquote>
<p>If the two sides already have different files, the result might not be what you expect.<br>
Run with <code>--dry-run</code> first to preview what will happen. Back up anything important before proceeding.</p>
</blockquote>
<p>I also recommend using <code>--check-access</code>. It requires a file called <code>RCLONE_TEST</code> in both folders. If it is missing, the sync will stop. This is a simple safeguard against mistakes like a wrong path or an unmounted drive. Create the files first:</p>
<figure raw="# Create locally
touch /Users/xin/Documents/SyncTest/RCLONE_TEST

# Create on OneDrive
rclone touch onedrive:SyncTest/RCLONE_TEST
" data-language="bash" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="bash" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="bash" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">#</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Create locally</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">touch</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> /Users/xin/Documents/SyncTest/RCLONE_TEST</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">#</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Create on OneDrive</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">rclone</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> touch</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> onedrive:SyncTest/RCLONE_TEST</span></span></code></pre></figure>
<p>Then run the initial sync:</p>
<figure raw="rclone bisync /Users/xin/Documents/SyncTest onedrive:SyncTest \
  --resync \
  --check-access \
  --recover \
  --resilient \
  --create-empty-src-dirs \
  --max-delete 50 \
  --conflict-resolve newer \
  --verbose \
  --progress
" data-language="bash" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="bash" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="bash" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">rclone</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> bisync</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> /Users/xin/Documents/SyncTest</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> onedrive:SyncTest</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --resync</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --check-access</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --recover</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --resilient</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --create-empty-src-dirs</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --max-delete</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 50</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --conflict-resolve</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> newer</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --verbose</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --progress</span></span></code></pre></figure>
<p>Here is what each flag does:</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--resync</code></td>
<td>Rebuilds the sync baseline. Use on first run or after a corrupted state</td>
</tr>
<tr>
<td><code>--check-access</code></td>
<td>Checks for <code>RCLONE_TEST</code> on both sides before doing anything</td>
</tr>
<tr>
<td><code>--recover</code></td>
<td>Tries to recover from a previous interrupted sync instead of failing</td>
</tr>
<tr>
<td><code>--resilient</code></td>
<td>Keeps going if a single file fails, instead of stopping everything</td>
</tr>
<tr>
<td><code>--create-empty-src-dirs</code></td>
<td>Syncs empty folder creation and deletion</td>
</tr>
<tr>
<td><code>--max-delete</code></td>
<td>Limits how many files can be deleted in one run</td>
</tr>
<tr>
<td><code>--conflict-resolve newer</code></td>
<td>Keeps the newer file when both sides changed the same file</td>
</tr>
<tr>
<td><code>--track-renames</code></td>
<td>Detects renames instead of treating them as a delete and re-upload</td>
</tr>
<tr>
<td><code>--verbose</code></td>
<td>Prints detailed output, useful for debugging</td>
</tr>
<tr>
<td><code>--log-file</code></td>
<td>Writes output to a log file</td>
</tr>
<tr>
<td><code>--progress</code></td>
<td>Shows live progress while syncing</td>
</tr>
<tr>
<td><code>--dry-run</code></td>
<td>Simulates the sync without making any changes</td>
</tr>
</tbody>
</table>
<h2 id="daily-sync"><a href="#daily-sync">Daily sync</a></h2>
<p>After the first run, drop <code>--resync</code> and add <code>--track-renames</code>:</p>
<figure raw="rclone bisync /Users/xin/Documents/SyncTest onedrive:SyncTest \
  --check-access \
  --recover \
  --resilient \
  --create-empty-src-dirs \
  --max-delete 50 \
  --conflict-resolve newer \
  --track-renames \
  --log-file=/tmp/rclone-bisync.log \
  --progress
" data-language="bash" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="bash" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="bash" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">rclone</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> bisync</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> /Users/xin/Documents/SyncTest</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> onedrive:SyncTest</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --check-access</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --recover</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --resilient</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --create-empty-src-dirs</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --max-delete</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 50</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --conflict-resolve</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> newer</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --track-renames</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --log-file=/tmp/rclone-bisync.log</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --progress</span></span></code></pre></figure>
<h2 id="sync-script"><a href="#sync-script">Sync script</a></h2>
<p>Typing that command every time is tedious. I do not need automatic or scheduled syncing, so I wrote a short script to run it manually when I want.</p>
<figure raw="#!/bin/bash

LOCAL=&#x22;/Users/xin/Documents/SyncTest&#x22;
REMOTE=&#x22;onedrive:SyncTest&#x22;
LOG_FILE=&#x22;/tmp/rclone-bisync.log&#x22;

echo &#x22;🔄 Syncing $LOCAL <-> $REMOTE&#x22;

rclone bisync &#x22;$LOCAL&#x22; &#x22;$REMOTE&#x22; \
  --check-access \
  --recover \
  --resilient \
  --create-empty-src-dirs \
  --max-delete 50 \
  --conflict-resolve newer \
  --track-renames \
  --log-file=&#x22;$LOG_FILE&#x22; \
  --progress

if [ $? -eq 0 ]; then
  echo &#x22;✅ Done&#x22;
else
  echo &#x22;❌ Sync failed. Check the log: $LOG_FILE&#x22;
  exit 1
fi
" data-language="bash" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="bash" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="bash" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">#!</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic">/bin/bash</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">LOCAL</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"/Users/xin/Documents/SyncTest"</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">REMOTE</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"onedrive:SyncTest"</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">LOG_FILE</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"/tmp/rclone-bisync.log"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">echo</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "🔄 Syncing </span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">$LOCAL</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> &#x3C;-> </span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">$REMOTE</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">rclone</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> bisync</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">$LOCAL</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">$REMOTE</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --check-access</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --recover</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --resilient</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --create-empty-src-dirs</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --max-delete</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 50</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --conflict-resolve</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> newer</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --track-renames</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --log-file=</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">$LOG_FILE</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> \</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  --progress</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">if</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> [</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> $?</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> -eq</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> ];</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> then</span></span>
<span data-line=""><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">  echo</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "✅ Done"</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">else</span></span>
<span data-line=""><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">  echo</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "❌ Sync failed. Check the log: </span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">$LOG_FILE</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"</span></span>
<span data-line=""><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">  exit</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 1</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">fi</span></span></code></pre></figure>
<p>Make it executable before running:</p>
<figure raw="chmod +x sync.sh
" data-language="bash" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="bash" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="bash" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">chmod</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> +x</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> sync.sh</span></span></code></pre></figure>
<p>Just Enjoy it.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Digital/Rclone</category>
        </item>
        <item>
            <title><![CDATA[Theming My Blog With Rosé Pine]]></title>
            <link>https://shiyui.vercel.app/craft/theming-my-blog-with-rosé-pine</link>
            <guid isPermaLink="false">theming-my-blog-with-rosé-pine</guid>
            <pubDate>Fri, 13 Feb 2026 11:53:12 GMT</pubDate>
            <content:encoded><![CDATA[<p>When I rebuilt my blog theme <a href="https://github.com/chanshiyucx/zero" rel="nofollow noopener noreferrer" target="_blank">Zero</a> from scratch, I went with <a href="https://rosepinetheme.com/" rel="nofollow noopener noreferrer" target="_blank">Rosé Pine</a> as the color scheme. Their design philosophy really resonates with me:</p>
<blockquote>
<p>Something beautiful<br>
All natural pine, faux fur and a bit of soho vibes for the classy minimalist.</p>
</blockquote>
<p>Natural, restrained, with a touch of warmth, exactly what I was after. I've switched everything over to Rosé Pine now: VSCode, Antigravity, Obsidian, and naturally, my blog.</p>
<h2 id="the-palette"><a href="#the-palette">The Palette</a></h2>
<p>Rosé Pine uses semantic color naming where each color has a clear purpose:</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>base</code></td>
<td>Primary background</td>
</tr>
<tr>
<td><code>surface</code></td>
<td>Secondary background atop base</td>
</tr>
<tr>
<td><code>overlay</code></td>
<td>Tertiary background atop surface</td>
</tr>
<tr>
<td><code>muted</code></td>
<td>Low contrast foreground</td>
</tr>
<tr>
<td><code>subtle</code></td>
<td>Medium contrast foreground</td>
</tr>
<tr>
<td><code>text</code></td>
<td>High contrast foreground</td>
</tr>
<tr>
<td><code>love</code></td>
<td>Per favore ama tutti</td>
</tr>
<tr>
<td><code>gold</code></td>
<td>Lemon tea on a summer morning</td>
</tr>
<tr>
<td><code>rose</code></td>
<td>A beautiful yet cautious blossom</td>
</tr>
<tr>
<td><code>pine</code></td>
<td>Fresh winter greenery</td>
</tr>
<tr>
<td><code>foam</code></td>
<td>Saltwater tidepools</td>
</tr>
<tr>
<td><code>iris</code></td>
<td>Smells of groundedness</td>
</tr>
</tbody>
</table>
<p>You can find the full color values on their <a href="https://rosepinetheme.com/palette/ingredients/" rel="nofollow noopener noreferrer" target="_blank">palette</a>. With this palette, my theme definition stays incredibly clean, one line per color variable, with light and dark modes all in one place:</p>
<figure raw="/* https://rosepinetheme.com/palette/ingredients/ */
:root {
  --color-base: light-dark(hsl(32deg 57% 95%), hsl(246deg, 24%, 17%));
  --color-surface: light-dark(hsl(35deg 100% 98%), hsl(248deg, 24%, 20%));
  --color-overlay: light-dark(hsl(33deg 43% 91%), hsl(248deg, 21%, 26%));
  --color-muted: light-dark(hsl(257deg 9% 61%), hsl(249deg, 12%, 47%));
  --color-subtle: light-dark(hsl(248deg 12% 52%), hsl(248deg, 15%, 61%));
  --color-text: light-dark(hsl(248deg 19% 40%), hsl(245deg, 50%, 91%));
  --color-love: light-dark(hsl(343deg 35% 55%), hsl(343deg, 76%, 68%));
  --color-gold: light-dark(hsl(35deg 81% 56%), hsl(35deg, 88%, 72%));
  --color-rose: light-dark(hsl(3deg 53% 67%), hsl(2deg, 66%, 75%));
  --color-pine: light-dark(hsl(197deg 53% 34%), hsl(197deg, 48%, 47%));
  --color-foam: light-dark(hsl(189deg 30% 48%), hsl(189deg, 43%, 73%));
  --color-iris: light-dark(hsl(268deg 21% 57%), hsl(267deg, 57%, 78%));
}
" data-language="css" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="css" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="css" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">/*</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> https://rosepinetheme.com/palette/ingredients/ </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">*/</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">:</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">root</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-base</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">32</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 57</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 95</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">246</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 24</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 17</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-surface</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">35</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 100</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 98</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">248</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 24</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 20</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-overlay</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">33</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 43</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 91</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">248</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 21</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 26</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-muted</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">257</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 9</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 61</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">249</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 12</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 47</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-subtle</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">248</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 12</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 52</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">248</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 15</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 61</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-text</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">248</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 19</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 40</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">245</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 50</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 91</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-love</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">343</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 35</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 55</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">343</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 76</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 68</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-gold</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">35</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 81</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 56</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">35</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 88</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 72</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-rose</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">3</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 53</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 67</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">2</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 66</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 75</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-pine</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">197</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 53</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 34</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">197</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 48</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 47</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-foam</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">189</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 30</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 48</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">189</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 43</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 73</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-iris</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light-dark(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">268</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 21</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 57</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">),</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">267</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 57</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 78</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<h2 id="theme-switching"><a href="#theme-switching">Theme Switching</a></h2>
<p>The traditional approach to light/dark themes requires media queries or class names, maintaining separate color declarations:</p>
<figure raw="/* The old way: define each variable twice */
:root {
  --color-base: hsl(32deg 57% 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-base: hsl(246deg 24% 17%);
  }
}
" data-language="css" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="css" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="css" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">/*</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> The old way: define each variable twice </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">*/</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">:</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">root</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  --color-base</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">32</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 57</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 95</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">);</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">@</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">media</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> (</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">prefers-color-scheme</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> dark</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  :</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">root</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">    --color-base</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> hsl</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">246</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">deg</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 24</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 17</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">%</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">);</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>This works, but when you have lots of color variables, maintenance becomes a pain.</p>
<p>The <code>light-dark()</code> function was introduced in CSS Color Module Level 5. It takes two parameters for light and dark mode values:</p>
<figure raw="/* Basic syntax */
color: light-dark(<light-value>, <dark-value>);

/* Works with various color formats */
color: light-dark(black, white);
color: light-dark(rgb(0 0 0), rgb(255 255 255));
color: light-dark(var(--light), var(--dark));
" data-language="css" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="css" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="css" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">/*</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Basic syntax </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">*/</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">color: light-dark(&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">light-value</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">dark-value</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">/*</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Works with various color formats </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">*/</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">color: light-dark(black</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> white);</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">color: light-dark(rgb(0 0 0), rgb(255 255 255));</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">color: light-dark(var(--light), var(--dark));</span></span></code></pre></figure>
<p>Before using it, you need to declare <code>color-scheme</code> to tell the browser which color schemes your page supports:</p>
<figure raw=":root {
  color-scheme: light dark;
}
" data-language="css" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="css" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="css" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">:</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">root</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">  color-scheme</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> light dark</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>Compared to the traditional approach, <code>light-dark()</code> keeps both colors on the same line, less code and the relationship is immediately clear. Since 2024, major browsers have all shipped support, so it's production-ready.</p>
<p>If you look closely, you'll notice my site's favicon uses a gradient from Love to Gold—warm and cozy, which is exactly the feeling I'm going for.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Web/CSS</category>
        </item>
        <item>
            <title><![CDATA[Generating Unique Random Numbers Using Sets]]></title>
            <link>https://shiyui.vercel.app/craft/generating-unique-random-numbers-using-sets</link>
            <guid isPermaLink="false">generating-unique-random-numbers-using-sets</guid>
            <pubDate>Mon, 21 Jul 2025 13:57:19 GMT</pubDate>
            <content:encoded><![CDATA[<p>JavaScript's built-in <code>Math.random()</code> method is great for generating random floating-point numbers that you can convert to integers. However, it has one significant limitation: it can't guarantee uniqueness across multiple calls. If you need to generate a series of unique random numbers, you'll need a more sophisticated approach.</p>
<p>In this article, we'll explore how to solve this problem using JavaScript's <code>Set</code> object, which naturally ensures uniqueness among its elements.</p>
<h2 id="the-problem-with-random-generation"><a href="#the-problem-with-random-generation">The Problem with Random Generation</a></h2>
<p>When you call <code>Math.random()</code> multiple times, there's always a chance of getting duplicate values, especially when working with smaller ranges or generating many numbers. For example:</p>
<figure raw="// This might produce duplicates
const numbers: number[] = [];
for (let i = 0; i < 5; i++) {
  numbers.push(Math.floor(Math.random() * 10) + 1);
}
console.log(numbers); // Could be [3, 7, 3, 9, 1] - notice the duplicate 3
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">//</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> This might produce duplicates</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> numbers</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">[] </span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> []</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">for</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 5</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  numbers</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">push</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">Math</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">floor</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">Math</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">random</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">() </span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">*</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 10</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">+</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 1</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">console</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">log</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">numbers</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic"> //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Could be [3, 7, 3, 9, 1] - notice the duplicate 3</span></span></code></pre></figure>
<h2 id="leveraging-sets-for-uniqueness"><a href="#leveraging-sets-for-uniqueness">Leveraging Sets for Uniqueness</a></h2>
<p>Sets in JavaScript automatically handle uniqueness—they simply ignore attempts to add duplicate values. This makes them perfect for our use case. Here's our strategy:</p>
<ol>
<li>Create a Set to store our unique numbers</li>
<li>Define our parameters: how many numbers we need and the range to draw from</li>
<li>Generate and collect random numbers until we have enough unique values</li>
<li>Convert to array for easier manipulation</li>
</ol>
<p>Here's a robust function that generates unique random numbers:</p>
<figure raw="const generateRandomNumbers = (
  count: number,
  min: number,
  max: number,
): number[] => {
  const rangeSize = max - min + 1;
  // Validation: ensure we&#x27;re not asking for more numbers than possible
  if (count > rangeSize) {
    throw new Error(&#x22;Count cannot be greater than the size of the range&#x22;);
  }
  if (count <= 0) {
    throw new Error(&#x22;Count must be a positive number&#x22;);
  }
  if (min > max) {
    throw new Error(&#x22;Minimum value cannot be greater than maximum value&#x22;);
  }

  const uniqueNumbers: Set<number> = new Set();

  // Keep generating until we have enough unique numbers
  while (uniqueNumbers.size < count) {
    const random = Math.floor(Math.random() * rangeSize) + min;
    uniqueNumbers.add(random);
  }

  return Array.from(uniqueNumbers);
};

console.log(generateRandomNumbers(5, 5, 10));
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic"> generateRandomNumbers</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span></span>
<span data-line=""><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">  count</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">  min</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">  max</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">[] </span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> rangeSize</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> max</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> -</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> min</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 1</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Validation: ensure we're not asking for more numbers than possible</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">count</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> ></span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> rangeSize</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    throw</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> Error</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"Count cannot be greater than the size of the range"</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">count</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    throw</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> Error</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"Count must be a positive number"</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">min</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> ></span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> max</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    throw</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> Error</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"Minimum value cannot be greater than maximum value"</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> uniqueNumbers</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> Set</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">number</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">></span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> Set</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Keep generating until we have enough unique numbers</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  while</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">uniqueNumbers</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">size</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> count</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> random</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> Math</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">floor</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">Math</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">random</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">() </span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">*</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> rangeSize</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">+</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> min</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">    uniqueNumbers</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">add</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">random</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> Array</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">from</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">uniqueNumbers</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">console</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">log</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">generateRandomNumbers</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">5</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 5</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 10</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">))</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span></code></pre></figure>
<h2 id="performance-considerations"><a href="#performance-considerations">Performance Considerations</a></h2>
<p>This method works well for most use cases, but be aware that as the ratio of <code>count</code> to <code>rangeSize</code> approaches 1, the algorithm may need many iterations to find the remaining unique values.</p>
<p>For such scenarios, you might consider alternative approaches like:</p>
<ul>
<li>Generating all possible numbers and shuffling the array</li>
<li>Using a rejection sampling method with tracking</li>
</ul>
<h2 id="wrapping-up"><a href="#wrapping-up">Wrapping Up</a></h2>
<p>By combining JavaScript's <code>Math.random()</code> with the uniqueness properties of Sets, we can easily generate collections of unique random numbers. This approach is clean, readable, and handles edge cases gracefully.</p>
<p>The key takeaway? When you need uniqueness in randomness, don't fight against duplicates—use data structures that naturally prevent them.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Web/JavaScript</category>
        </item>
        <item>
            <title><![CDATA[Match Chinese Characters]]></title>
            <link>https://shiyui.vercel.app/craft/match-chinese-characters</link>
            <guid isPermaLink="false">match-chinese-characters</guid>
            <pubDate>Wed, 25 Dec 2024 16:11:36 GMT</pubDate>
            <content:encoded><![CDATA[<p>When searching for "JavaScript regex for matching Chinese characters" on Google, most results suggest using <code>/[\u4e00-\u9fa5]/</code>. But is this regular expression really correct? Let's dive in and find out.</p>
<h2 id="han-script-and-han-characters"><a href="#han-script-and-han-characters">Han Script and Han Characters</a></h2>
<p>Let's start by understanding two fundamental concepts:</p>
<ul>
<li><strong>Han Script</strong> is a writing system that originated from Chinese and was later adopted by Japanese, Korean, and other languages</li>
<li><strong>Han Characters (CJK Ideographs)</strong> are the basic units of Han Script</li>
</ul>
<p>Many countries and regions in the Han cultural sphere have developed their own character encoding standards. Unicode unifies these standards, aiming to achieve lossless conversion between original standards and Unicode encoding.</p>
<h2 id="character-sets--encodings"><a href="#character-sets--encodings">Character Sets &#x26; Encodings</a></h2>
<p>What's the difference between Unicode, GBK, and UTF-8? They are actually concepts from different domains.</p>
<h3 id="character-sets"><a href="#character-sets">Character Sets</a></h3>
<p>Common character sets like Unicode and ASCII are designed to represent characters with a series of numbers, also known as code points.</p>
<p>ASCII uses one byte to represent a character, defining encodings for 128 characters that correspond to English characters and binary values.</p>
<p>For Asian languages like Chinese, more bytes are needed to represent a single character. For example, GB2312 (for Simplified Chinese) uses two bytes per character, allowing representation of up to 65,536 characters (256 x 256).</p>
<p>The existence of multiple encoding systems meant that the same binary number could be interpreted as different symbols. Reading text with the wrong encoding results in garbled characters. This is why Unicode was created.</p>
<p>Unicode is a unified character set that assigns a unique code to every symbol in the world. This uniqueness eliminates character encoding confusion.</p>
<h3 id="character-encodings"><a href="#character-encodings">Character Encodings</a></h3>
<p>Unicode is just a character set - it defines binary codes for symbols but doesn't specify how to store these codes. To save characters in computers, they must first be encoded.</p>
<p>UTF-8 is one of Unicode's encoding methods and the most widely used on the internet. Other implementations include UTF-16 (using two or four bytes per character) and UTF-32 (using four bytes per character).</p>
<p>UTF-8's main feature is its variable-length encoding, using 1-4 bytes per symbol depending on the character.</p>
<p>GBK (Chinese Internal Code Specification) is similar to UTF-8 but specifically for Chinese characters. It's an extension of GB2312.</p>
<p>In summary: <strong>ASCII and Unicode are character sets, while UTF-8 and GBK are encoding methods</strong>. They serve different purposes and cannot be directly compared.</p>
<p>In summary:</p>
<ul>
<li><strong>Character sets (e.g., Unicode, ASCII)</strong> define mappings between characters and codes.</li>
<li><strong>Encodings (e.g., UTF-8, GBK)</strong> specify how those codes are stored.</li>
</ul>
<h2 id="regex-for-chinese-characters"><a href="#regex-for-chinese-characters">Regex for Chinese Characters</a></h2>
<p>Now that we understand character sets and encodings, let's return to our main topic. What exactly do we mean by "Chinese characters" in terms of character sets?</p>
<p>Unicode unifies characters from different encoding standards based on three dimensions: semantics, abstract shape, and typeface. Characters with the same origin, meaning, and similar shapes are given the same encoding. These encoded characters are called CJK Unified Ideographs, which we referred to as "Han Characters" earlier.</p>
<p>The regex <code>/[\u4e00-\u9fa5]/</code> matches the CJK Unified Ideographs block included in Unicode 1.0.1. Before Unicode 3.0 (1992-1999), this expression correctly matched all Han characters. This regex is likely over 20 years old.</p>
<p>However, Unicode has evolved significantly, with version 10.0.0 released in June 2017. Many new Han characters have been added over these 20 years, falling outside the range of this regex. We need a modern solution that keeps up with Unicode standards.</p>
<p>Here's a maintenance-free regex for matching Han characters:</p>
<figure raw="const HanZi = /\p{Unified_Ideograph}/u;

!!&#x22;你好&#x22;.match(HanZi); // true
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> HanZi</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> /</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">\p</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">{Unified_Ideograph}/</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">u</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">!!</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"你好"</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">match</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">HanZi</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic"> //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> true</span></span></code></pre></figure>
<p>It's called <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes" rel="nofollow noopener noreferrer" target="_blank">Unicode property escapes</a> and already available in <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape#browser_compatibility" rel="nofollow noopener noreferrer" target="_blank">Chrome 64, Firefox 78, Safari 11.1 and Node.js 10</a>.</p>
<ul>
<li><code>\u</code> is a regex flag defined in ECMAScript 2015, treating the pattern as Unicode code point sequences</li>
<li><code>\p</code> is a Unicode property escape defined in ECMAScript 2018, enabling pattern construction based on Unicode character properties</li>
<li><code>Unified_Ideograph</code> is a binary property of Unicode characters: Yes for Han characters, No for others</li>
</ul>
<p><code>\p{Unified_Ideograph}</code> matches all Unicode characters with <code>Unified_Ideograph=yes</code>. The implementation depends on the Unicode version of the runtime, freeing developers from maintaining specific code point ranges.</p>
<h2 id="similar-unicode-property-escapes"><a href="#similar-unicode-property-escapes">Similar Unicode Property Escapes</a></h2>
<figure raw="/\p{Ideographic}/u;
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">/</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">\p</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">{Ideographic}/</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">u</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span></code></pre></figure>
<p>This matches all characters with <code>Ideographic=yes</code>, including CJK Unified Ideographs, Tangut characters and components, Nüshu, CJK compatibility characters, Suzhou numerals, " 〇 ", and the Japanese letter-ending mark "〆".</p>
<p>Using <code>/\p{Ideographic}/u</code> is too broad for matching Han characters as it includes Tangut, Nüshu, and compatibility characters.</p>
<figure raw="/\p{Script=Han}/u;
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">/</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">\p</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">{Script=Han}/</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">u</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span></code></pre></figure>
<p>The <code>Script</code> property selects characters that:</p>
<ol>
<li>Share common graphical features and historical development</li>
<li>Are used to represent textual information in a writing system</li>
</ol>
<p><code>Script=Han</code> includes all CJK Unified Ideographs, compatibility characters, Suzhou numerals, " 〇 ", "〆", " 々 ", and commonly used radicals in dictionaries.</p>
<p><code>/\p{Script=Han}/u</code> matches all characters in the Han Script, including radicals and modifiers that either lack independent meaning or cannot exist independently. This regex confuses the scope of Han Script with Han Characters.</p>
<h2 id="summary"><a href="#summary">Summary</a></h2>
<ol>
<li><code>/[\u4e00-\u9fa5]/</code> is outdated, don't use this 20-year-old regex</li>
<li><code>/\p{Unified_Ideograph}/u</code> is correct, maintenance-free and matches all Han characters</li>
<li><code>/\p{Ideographic}/u</code> and <code>/\p{Script=Han}/u</code> match additional characters beyond Han characters and are incorrect for this specific purpose</li>
</ol>
<p>Ref:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape" rel="nofollow noopener noreferrer" target="_blank">Unicode character class escape</a></li>
<li><a href="https://www.regular-expressions.info/unicode.html" rel="nofollow noopener noreferrer" target="_blank">Unicode Regular Expressions</a></li>
</ul>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Web/JavaScript</category>
            <category>JavaScript/RegExp</category>
        </item>
        <item>
            <title><![CDATA[Calculating String Bytes Count]]></title>
            <link>https://shiyui.vercel.app/craft/calculating-string-bytes-count</link>
            <guid isPermaLink="false">calculating-string-bytes-count</guid>
            <pubDate>Mon, 18 Mar 2024 11:25:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Recently, I encountered a challenging problem at work. I was responsible for developing the file upload function, which stores multiple versions of a file based on the filename. When uploading a file, the total number of bytes in the filename is counted and spliced into the name as an identifier.</p>
<p>At first I used <code>string.length</code> as the filename byte count, which resulted in an identifier error. After I checked the wiki documentation, I have a new perception of character encoding.</p>
<h2 id="unicode-code-points"><a href="#unicode-code-points">Unicode Code Points</a></h2>
<p>The <code>charCodeAt()</code> method of String values returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.</p>
<p>The <code>codePointAt()</code> method of String values returns a non-negative integer that is the Unicode code point value of the character starting at the given index. Note that the index is still based on UTF-16 code units, not Unicode code points.</p>
<p>Unicode code points range from 0 to 1114111 (0x10FFFF). <code>charCodeAt()</code> always returns a value that is less than 65536, because <strong>the higher code points are represented by a pair of 16-bit surrogate pseudo-characters</strong>. Therefore, in order to get a full character with value greater than 65535, it is necessary to retrieve not only <code>charCodeAt(i)</code>, but also <code>charCodeAt(i + 1)</code>, or to use <code>codePointAt(i)</code> instead.</p>
<p>So we use <code>codePointAt(i)</code> method to get the Unicode code point value at the given index.</p>
<figure raw="const charCode = str.codePointAt(i)
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">codePointAt</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">i</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span></code></pre></figure>
<h2 id="utf-8"><a href="#utf-8">UTF-8</a></h2>
<p>UTF-8 is a variable-length Unicode encoding format that uses one to four bytes to encode each character.In the following table, the x characters are replaced by the bits of the code point:</p>
<table>
<thead>
<tr>
<th align="left">First code point</th>
<th align="left">Last code point</th>
<th align="left">Byte 1</th>
<th align="left">Byte 2</th>
<th align="left">Byte 3</th>
<th align="left">Byte 4</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">U+0000</td>
<td align="left">U+007F</td>
<td align="left">0xxxxxxx</td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left">U+0080</td>
<td align="left">U+07FF</td>
<td align="left">110xxxxx</td>
<td align="left">10xxxxxx</td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left">U+0800</td>
<td align="left">U+FFFF</td>
<td align="left">1110xxxx</td>
<td align="left">10xxxxxx</td>
<td align="left">10xxxxxx</td>
<td align="left"></td>
</tr>
<tr>
<td align="left">U+010000</td>
<td align="left">[b]U+10FFFF</td>
<td align="left">11110xxx</td>
<td align="left">10xxxxxx</td>
<td align="left">10xxxxxx</td>
<td align="left">10xxxxxx</td>
</tr>
</tbody>
</table>
<p>Information about UTF-8 encoding on Wikipedia: <a href="https://en.wikipedia.org/wiki/UTF-8#Encoding" rel="nofollow noopener noreferrer" target="_blank">UTF-8 Encoding</a>.</p>
<p>So we can count the number of bytes in each code point character in segments and then add them up to get the total number.</p>
<p>It's important to note that the higher code points are represented by a pair of 16-bit surrogate pseudo-characters, so when the UTF-16 code units is greater than 0x10FFFF, the index should be an additional 1.</p>
<figure raw="/**
 * @description Calculating the number of bytes in a UTF-8 encoded string
 * @param  str - Target string
 * @return The number of bytes in the target string
 */
const stringByteUTF8 = (str: string): number => {
  let total = 0
  let charCode: number
  for (let i = 0, len = str.length; i < len; i++) {
    charCode = str.codePointAt(i)

    if (charCode <= 0x007f) {
      total += 1
    } else if (charCode <= 0x07ff) {
      total += 2
    } else if (charCode <= 0xffff) {
      total += 3
    } else {
      total += 4
      i++ // the index should be an additional 1
    }
  }
  return total
}
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">/**</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">description</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Calculating the number of bytes in a UTF-8 encoded string</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">param</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  str</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> - Target string</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">return</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> The number of bytes in the target string</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic"> */</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic"> stringByteUTF8</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> (</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> string</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  for</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> len</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">length</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> len</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">    charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">codePointAt</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">i</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0x007f</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 1</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0x07ff</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 2</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0xffff</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 3</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 4</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic"> //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> the index should be an additional 1</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> total</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<h2 id="utf-16"><a href="#utf-16">UTF-16</a></h2>
<p>UTF-16 encodes up to 65535 with two bytes, and beyond 65535 with four bytes.</p>
<p>So the code to calculate the number of bytes in a UTF16 string is relatively simple.</p>
<figure raw="/**
 * @description Calculating the number of bytes in a UTF-16 encoded string
 * @param  str - Target string
 * @return The number of bytes in the target string
 */
const stringByteUTF16 = (str: string): number => {
  let total = 0
  let charCode: number
  for (let i = 0, len = str.length; i < len; i++) {
    charCode = str.codePointAt(i)

    if (charCode <= 0xffff) {
      total += 2
    } else {
      total += 4
      i++
    }
  }
  return total
}
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">/**</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">description</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Calculating the number of bytes in a UTF-16 encoded string</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">param</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  str</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> - Target string</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">return</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> The number of bytes in the target string</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic"> */</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic"> stringByteUTF16</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> (</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> string</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  for</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> len</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">length</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> len</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">    charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">codePointAt</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">i</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0xffff</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 2</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 4</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> total</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<h2 id="final-code"><a href="#final-code">Final Code</a></h2>
<figure raw="/**
 * @description Calculating the number of bytes in a string
 * @param  str - Target string
 * @param  charset - Encoding format of the target string
 * @return The number of bytes in the target string
 */
type Charset = &#x22;utf-8&#x22; | &#x22;utf-16&#x22;

const stringByte = (str: string, charset: Charset = &#x22;utf-8&#x22;) => {
  let total = 0
  let charCode: number

  if (charset === &#x22;utf-8&#x22;) {
    for (let i = 0, len = str.length; i < len; i++) {
      charCode = str.codePointAt(i)

      if (charCode <= 0x007f) {
        total += 1
      } else if (charCode <= 0x07ff) {
        total += 2
      } else if (charCode <= 0xffff) {
        total += 3
      } else {
        total += 4
        i++
      }
    }
  } else if (charset === &#x22;utf-16&#x22;) {
    for (let i = 0, len = str.length; i < len; i++) {
      charCode = str.codePointAt(i)

      if (charCode <= 0xffff) {
        total += 2
      } else {
        total += 4
        i++
      }
    }
  }

  return total
}
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">/**</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">description</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Calculating the number of bytes in a string</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">param</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  str</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> - Target string</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">param</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  charset</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> - Encoding format of the target string</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> * </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">@</span><span style="--shiki-light:#286983;--shiki-light-font-style:italic;--shiki-dark:#3E8FB0;--shiki-dark-font-style:italic">return</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> The number of bytes in the target string</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic"> */</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">type</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> Charset</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "utf-8"</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "utf-16"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic"> stringByte</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> (</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> string</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> charset</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> Charset</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "utf-8"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charset</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> ===</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "utf-8"</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    for</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> len</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">length</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> len</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">codePointAt</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">i</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">      if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0x007f</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">        total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 1</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">      }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0x07ff</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">        total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 2</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">      }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0xffff</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">        total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 3</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">      }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">        total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 4</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">        i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">      }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charset</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> ===</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "utf-16"</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    for</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">let</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> len</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">length</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> len</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> str</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">codePointAt</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">i</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">      if</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">charCode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x3C;=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0xffff</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">        total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 2</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">      }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> else</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">        total</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +=</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 4</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">        i</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">++</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">      }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> total</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>Q.E.D.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Web/Unicode</category>
        </item>
        <item>
            <title><![CDATA[Hidden Tricks with AbortController]]></title>
            <link>https://shiyui.vercel.app/craft/hidden-tricks-with-abortcontroller</link>
            <guid isPermaLink="false">hidden-tricks-with-abortcontroller</guid>
            <pubDate>Sat, 11 Jan 2025 22:44:14 GMT</pubDate>
            <content:encoded><![CDATA[<p>Today, I'd like to talk about one of the standard JavaScript APIs you are likely sleeping on. It's called <code>AbortController</code>.</p>
<h2 id="what-isabortcontroller"><a href="#what-isabortcontroller">What is AbortController?</a></h2>
<p>The <strong><code>AbortController</code></strong> interface represents a controller object that allows you to abort anything when desired.</p>
<p>Here's how you use it:</p>
<figure raw="const controller = new AbortController()

controller.signal
controller.abort()
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> AbortController</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">abort</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span></code></pre></figure>
<p>Creating a controller instance provides you with two key components:</p>
<ul>
<li><code>AbortController.signal</code>: an instance of <code>AbortSignal</code>, which can be used to communicate with, or to abort, an asynchronous operation.</li>
<li><code>AbortController.abort()</code>: aborts an asynchronous operation before it has completed. When called, triggers the abort event on the <code>signal</code>. It also updates the signal to be marked as aborted.</li>
</ul>
<p>You might wonder where the actual abort logic resides. Here's the elegant part—you define it! Simply listen for the <code>abort</code> event and implement your cancellation logic as needed:</p>
<figure raw="controller.signal.addEventListener(&#x22;abort&#x22;, () => {
  // Implement the abort logic.
})
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">addEventListener</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"abort"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> ()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Implement the abort logic.</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span></code></pre></figure>
<p>Now, let's explore which standard JavaScript APIs have built-in support for <code>AbortSignal</code>.</p>
<h2 id="usage"><a href="#usage">Usage</a></h2>
<h3 id="event-listeners"><a href="#event-listeners">Event listeners</a></h3>
<p>You can provide an abort <code>signal</code> when adding an event listener for it to be automatically removed once the abort happens.</p>
<p>For example, calling <code>controller.abort()</code> removes the <code>resize</code> listener from the window. That is an extremely elegant way of handling event listeners because you no longer need to separately define the listener function for later removal with <code>.removeEventListener()</code>.</p>
<figure raw="const listener = () => {}

// window.addEventListener(&#x27;resize&#x27;, listener)
// window.removeEventListener(&#x27;resize&#x27;, listener)

const controller = new AbortController()
window.addEventListener(&#x27;resize&#x27;, listener, { signal: controller.signal })
controller.abort()
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic"> listener</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> ()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">//</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> window.addEventListener('resize', listener)</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">//</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> window.removeEventListener('resize', listener)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> AbortController</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">window</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">addEventListener</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">'resize'</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> listener</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> }</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">abort</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span></code></pre></figure>
<p>This pattern is especially useful when different parts of your application need to manage listener cleanup, as passing around an <code>AbortController</code> instance is much cleaner than sharing function references.</p>
<p>One of the most powerful features is the ability to use a single signal to manage multiple event listeners simultaneously!</p>
<figure raw="useEffect(() => {
  const controller = new AbortController()

  window.addEventListener(&#x22;resize&#x22;, handleResize, {
    signal: controller.signal,
  })
  window.addEventListener(&#x22;hashchange&#x22;, handleHashChange, {
    signal: controller.signal,
  })
  window.addEventListener(&#x22;storage&#x22;, handleStorageChange, {
    signal: controller.signal,
  })

  return () => {
    // Calling &#x60;.abort()&#x60; removes ALL event listeners
    // associated with &#x60;controller.signal&#x60;. Gone!
    controller.abort()
  }
}, [])
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">useEffect</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> AbortController</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  window</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">addEventListener</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"resize"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> handleResize</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">    signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  window</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">addEventListener</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"hashchange"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> handleHashChange</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">    signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  window</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">addEventListener</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"storage"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> handleStorageChange</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">    signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> ()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">    //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Calling `.abort()` removes ALL event listeners</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">    //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> associated with `controller.signal`. Gone!</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">    controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">abort</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">},</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> [])</span></span></code></pre></figure>
<p>The example above demonstrates how a single <code>useEffect()</code> hook can manage multiple event listeners with different purposes. The cleanup is beautifully simple - one call to <code>controller.abort()</code> removes all listeners at once!</p>
<h3 id="fetch-requests"><a href="#fetch-requests">Fetch requests</a></h3>
<p>The <code>fetch()</code> API also integrates seamlessly with <code>AbortSignal</code>. When an abort event is triggered, any pending request will be cancelled, and its promise will reject.</p>
<figure raw="function uploadFile(file: File) {
  const controller = new AbortController()

  // Provide the abort signal to this fetch request
  // so it can be aborted anytime be calling &#x60;controller.abort()&#x60;.
  const response = fetch(&#x22;/upload&#x22;, {
    method: &#x22;POST&#x22;,
    body: file,
    signal: controller.signal,
  })

  return { response, controller }
}
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> uploadFile</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">file</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> File</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> AbortController</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Provide the abort signal to this fetch request</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> so it can be aborted anytime be calling `controller.abort()`.</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> response</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> fetch</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"/upload"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">    method</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "POST"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">    body</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> file</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">    signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> response</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>In this example, <code>uploadFile()</code> starts a file upload and returns both the response promise and the controller. This makes it easy to cancel uploads when needed, such as when users click a "Cancel" button.</p>
<p><code>AbortSignal</code> provides several convenient static methods that make request handling even easier.</p>
<h4 id="abortsignaltimeout"><a href="#abortsignaltimeout"><code>AbortSignal.timeout</code></a></h4>
<p>The <code>AbortSignal.timeout()</code> method offers a concise way to create time-limited operations. It automatically aborts after the specified duration - perfect for adding request timeouts without manual <code>AbortController</code> setup:</p>
<figure raw="fetch(url, {
  // Abort this request automatically if it takes
  // more than 3000ms to complete.
  signal: AbortSignal.timeout(3000),
})
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">fetch</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">url</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Abort this request automatically if it takes</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> more than 3000ms to complete.</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">  signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> AbortSignal</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">timeout</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">3000</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span></code></pre></figure>
<h4 id="abortsignalany"><a href="#abortsignalany"><code>AbortSignal.any</code></a></h4>
<p>Similar to how you can use <code>Promise.race()</code> to handle multiple promises on a first-come-first-served basis, you can utilize the <code>AbortSignal.any()</code> static method to group multiple abort signals into one.</p>
<figure raw="const publicController = new AbortController()
const internalController = new AbortController()

channel.addEventListener(&#x22;message&#x22;, handleMessage, {
  signal: AbortSignal.any([publicController.signal, internalController.signal]),
})
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> publicController</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> AbortController</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> internalController</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> AbortController</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">channel</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">addEventListener</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"message"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> handleMessage</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">  signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> AbortSignal</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">any</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">([</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">publicController</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> internalController</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">])</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span></code></pre></figure>
<p>The example above shows two controllers working together: a public one that lets code consumers trigger aborts, and an internal one for our own cleanup needs. Either controller can remove the message listener independently.</p>
<p>When any signal provided to <code>AbortSignal.any()</code> aborts, it triggers the combined signal to abort as well. Once aborted, subsequent abort events from other signals are ignored.</p>
<h3 id="streams"><a href="#streams">Streams</a></h3>
<p>You can use <code>AbortController</code> and <code>AbortSignal</code> to cancel streams as well.</p>
<figure raw="const stream = new WritableStream({
  write(chunk, controller) {
    controller.signal.addEventListener(&#x27;abort&#x27;, () => {
      // Handle stream abort here.
    })
  },
})

const writer = stream.getWriter()
await writer.abort()
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> stream</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> WritableStream</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">  write</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">chunk</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">    controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">addEventListener</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">'abort'</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> ()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">      //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Handle stream abort here.</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  },</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> writer</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> stream</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">getWriter</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">await</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> writer</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">abort</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span></code></pre></figure>
<p>The <code>WritableStream</code> controller provides a <code>signal</code> property that works just like any other <code>AbortSignal</code>. Calling <code>writer.abort()</code> triggers the abort event on this signal, allowing for clean stream cancellation.</p>
<h2 id="abort-error-handling"><a href="#abort-error-handling">Abort error handling</a></h2>
<p>Each abort event can carry a custom reason, enabling you to handle different cancellation scenarios in distinct ways.</p>
<p>You can provide an abort reason when calling <code>controller.abort()</code>, and later access it through the signal's <code>reason</code> property to determine why the operation was cancelled.</p>
<figure raw="const controller = new AbortController()

controller.signal.addEventListener(&#x22;abort&#x22;, () => {
  console.log(controller.signal.reason) // &#x22;user cancellation&#x22;
})

// Provide a custom reason to this abort.
controller.abort(&#x22;user cancellation&#x22;)
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> new</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> AbortController</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">addEventListener</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"abort"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> ()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">  console</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">log</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">signal</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">reason</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">) </span><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">//</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> "user cancellation"</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">//</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> Provide a custom reason to this abort.</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">controller</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">abort</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"user cancellation"</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span></code></pre></figure>
<p>The <code>reason</code> argument can be any JavaScript value so you can pass strings, errors, or even objects.</p>
<p>Ref:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController" rel="nofollow noopener noreferrer" target="_blank">MDN AbortController</a></li>
<li><a href="https://kettanaito.com/blog/dont-sleep-on-abort-controller" rel="nofollow noopener noreferrer" target="_blank">Don't Sleep on AbortController</a></li>
</ul>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Web/JavaScript</category>
            <category>JavaScript/AbortController</category>
        </item>
        <item>
            <title><![CDATA[JSX.Element vs ReactNode vs ReactElement]]></title>
            <link>https://shiyui.vercel.app/craft/jsxelement-vs-reactnode-vs-reactelement</link>
            <guid isPermaLink="false">jsxelement-vs-reactnode-vs-reactelement</guid>
            <pubDate>Sun, 11 Aug 2024 23:22:08 GMT</pubDate>
            <content:encoded><![CDATA[<p>When to use JSX.Element vs ReactNode vs ReactElement?</p>
<figure raw="<p> // ReactElement = JSX.Element
  <Custom> // ReactElement = JSX.Element
     {true &#x26;&#x26; &#x22;test&#x22;} // ReactNode
  </Custom>
</p>
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> // ReactElement = JSX.Element</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">  &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">Custom</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> // ReactElement = JSX.Element</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">     {</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">true</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x26;&#x26;</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> "test"</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> // ReactNode</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">  &#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">Custom</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span></code></pre></figure>
<p>A ReactElement is an object with a type and props.</p>
<figure raw="type Key = string | number

interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
  type: T
  props: P
  key: Key | null
}
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">type</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> Key</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> string</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">interface</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactElement</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">P</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> any</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> T</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> extends</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> string</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> JSXElementConstructor</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">any</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">></span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> string</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> JSXElementConstructor</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">any</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">>></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  type</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> T</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  props</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> P</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">  key</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> Key</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> null</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>A ReactNode is a ReactElement, a ReactFragment, a string, a number or an array of ReactNodes, or null, or undefined, or a boolean.</p>
<figure raw="type ReactText = string | number
type ReactChild = ReactElement | ReactText

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">type</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactText</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> string</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> number</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">type</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactChild</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactElement</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactText</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">interface</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactNodeArray</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> extends</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> Array</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">ReactNode</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {}</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">type</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactFragment</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {}</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactNodeArray</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">type</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactNode</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactChild</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactFragment</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactPortal</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> boolean</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> null</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> undefined</span></span></code></pre></figure>
<p>JSX.Element is a ReactElement, with the generic type for props and type being any. so they are more or less the same.</p>
<figure raw="declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> {}
  }
}
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">declare</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> global</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  namespace</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> JSX</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">    interface</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> Element</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> extends</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> React</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">ReactElement</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">any</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> any</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {}</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>Components return:</p>
<figure raw="render(): ReactNode;
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">render</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(): </span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">ReactNode</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span></code></pre></figure>
<p>And functions are "stateless components":</p>
<figure raw="interface StatelessComponent<P = {}> {
  (props: P &#x26; { children?: ReactNode }, context?: any): ReactElement | null
  // … doesn&#x27;t matter
}
" data-language="typescript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="typescript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">interface</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> StatelessComponent</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">P</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {}></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  (</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> P</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x26;</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic"> children</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">?:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactNode</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> },</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> context</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">?:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> any</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">:</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> ReactElement</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> |</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> null</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">  //</span><span style="--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic"> … doesn't matter</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<ul>
<li>TS class component: returns ReactNode with render(), more permissive than React/JS</li>
<li>TS function component: returns JSX.Element | null, more restrictive than React/JS</li>
</ul>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Web/React</category>
        </item>
        <item>
            <title><![CDATA[Stagger Enter Animation]]></title>
            <link>https://shiyui.vercel.app/craft/stagger-enter-animation</link>
            <guid isPermaLink="false">stagger-enter-animation</guid>
            <pubDate>Thu, 25 Sep 2025 20:17:45 GMT</pubDate>
            <content:encoded><![CDATA[<p>Recently I was redesigning my blog, I added stagger enter animations to all pages using Framer Motion.</p>
<p>A stagger animation makes child elements animate one after another with a delay. Instead of all animating at once, each element appears in sequence, creating a smooth, rhythmic effect.</p>
<p>While looking for blog design inspiration, I noticed that <a href="https://paco.me/" rel="nofollow noopener noreferrer" target="_blank">Paco Coursey</a> blog and the <a href="https://rosepinetheme.com/" rel="nofollow noopener noreferrer" target="_blank">Rosé Pine</a> official website had much smoother stagger enter animations. So I dove into their source code to see how they implemented it.</p>
<h2 id="how-it-works"><a href="#how-it-works">How It Works</a></h2>
<p>After studying the source code, I found the implementation is pretty simple. The core idea is using CSS variables and animation delays to control when each element enters.</p>
<figure raw="[data-animate] {
    --stagger: 0;
    --delay: 120ms;
    --start: 0ms;
    animation: enter 0.6s both;
    animation-delay: calc(var(--stagger) * var(--delay) + var(--start));
}

@keyframes enter {
    0% {
        opacity: 0;
        transform: translateY(10px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}
" data-language="css" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="css" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="css" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">[</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">data-animate</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">]</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">    --stagger</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">    --delay</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 120</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">ms</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">    --start</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">ms</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">    animation</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> enter </span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">0.6</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">s</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> both</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">    animation-delay</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> calc</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">var</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">--stagger</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> *</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> var</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">--delay</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> var</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">--start</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">));</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">@</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">keyframes</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> enter</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">    0% </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">        opacity</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">        transform</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> translateY</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">10</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">px</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">);</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">    to </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">        opacity</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 1</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">        transform</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> none</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    }</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>Then in HTML, you use it like this:</p>
<figure raw="<p style=&#x22;--stagger: 1&#x22; data-animate>Hello, I&#x27;m Shiyu.</p>
<p style=&#x22;--stagger: 2&#x22; data-animate>A curious soul with big dreams.</p>
<p style=&#x22;--stagger: 3&#x22; data-animate>Full-Stack Developer</p>
" data-language="html" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="html" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="html" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> style</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">=</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"--stagger: 1"</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> data-animate</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">Hello, I'm Shiyu.</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> style</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">=</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"--stagger: 2"</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> data-animate</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">A curious soul with big dreams.</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> style</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">=</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"--stagger: 3"</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> data-animate</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">Full-Stack Developer</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span></code></pre></figure>
<p>The core principle is calculating different delay times for each element. Each element has a <code>--stagger</code> value representing its position in the sequence. When multiplied by a fixed delay interval, elements play their enter and upward slide animations in order.</p>
<p>Since this approach uses native CSS animations, it performs much better and feels smoother than Framer Motion's stagger animations. I immediately switched to this native solution.</p>
<h2 id="automation-approach"><a href="#automation-approach">Automation Approach</a></h2>
<p>While implementing the new animations, I realized that manually adding <code>data-animate</code> and setting <code>--stagger</code> values for every element was a real pain. It gets even worse when you need to add animations to content rendered by third-party components, like MDX components, which requires invasive modifications. So I started looking for simpler automation approaches.</p>
<p>My first thought was <code>CSS Counters</code>, which can automatically increment variables based on usage count, achieving auto-incrementing <code>--stagger</code> values that can even work across element hierarchies—similar to how Framer Motion works.</p>
<p>But reality hit hard. As of now, variables defined by <code>CSS Counters</code> can't participate in <code>calc()</code> method calculations because counter values are treated as string types and can't be used in mathematical operations.</p>
<p>Just when I was about to give up, I discovered the <code>sibling-index()</code> function in a <a href="https://github.com/w3c/csswg-drafts/issues/1026" rel="nofollow noopener noreferrer" target="_blank">GitHub issue</a>. It returns the current element's index within its parent element, and unlike counters, this value can actually be used in calculations.</p>
<p>This allows the code above to be greatly simplified:</p>
<figure raw="
.animate-auto {
	--delay: 120ms;
	--start: 0ms;
}

.animate-auto > p {
	--stagger: sibling-index();
	animation: enter 0.6s both;
	animation-delay: calc(var(--stagger) * var(--delay) + var(--start));
}
" data-language="css" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="css" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="css" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">.</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">animate-auto</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">	--delay</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 120</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">ms</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">	--start</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> 0</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">ms</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic">.</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">animate-auto</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> ></span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8"> p</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-light-font-style:italic;--shiki-dark:#EA9A97;--shiki-dark-font-style:italic">	--stagger</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> sibling-index(</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">	animation</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> enter </span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">0.6</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">s</span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177"> both</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">	animation-delay</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">:</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> calc</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic">var</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">--stagger</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> *</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> var</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">--delay</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> +</span><span style="--shiki-light:#B4637A;--shiki-light-font-style:italic;--shiki-dark:#EB6F92;--shiki-dark-font-style:italic"> var</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">--start</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">));</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>All you have to do is add <code>animate-auto</code> to the parent element, and all child elements automatically get the animation effect. The only limitation is that it can only calculate sibling element indices, so it can't add animations across hierarchical levels. But I'm already quite satisfied with the current effect—what more could I ask for?</p>
<p>If you also want to add this kind of smooth stagger animation to your website, give this approach a try!</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>CSS/Animation</category>
        </item>
        <item>
            <title><![CDATA[ForwardRef Escape Hatches]]></title>
            <link>https://shiyui.vercel.app/craft/forwardref-escape-hatches</link>
            <guid isPermaLink="false">forwardref-escape-hatches</guid>
            <pubDate>Mon, 18 Mar 2024 11:24:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In software development, an escape hatch is a mechanism that allows a developer to bypass or override certain constraints or limitations of the system. Escape hatches are typically used in situations where the developer needs to access features or functionality that is not directly exposed by the system's API or user interface.</p>
<p>In react, both useEffect and useRef are marked as an escape hatch.</p>
<h2 id="why-escape-hatches"><a href="#why-escape-hatches">Why Escape Hatches?</a></h2>
<p>Why are ref and effect categorized in the escape Hatches? This is because both operate on factors that are 「out of React's control」.</p>
<p>What is handled in effect is the side effects. For example:</p>
<ul>
<li><code>document.title</code> is modified in useEffect. <code>document.title</code> does not belong to the state in React, React can not sense his changes, so it is classified in effect.</li>
<li>making the DOM focus requires calling <code>element.focus()</code>, and the direct execution of the DOM API is not controlled by React.</li>
</ul>
<p>Although they are factors out of React's control, React also wants to prevent them from getting out of control as much as possible in order to ensure the robustness of the application.</p>
<p>To use the ref prop, you can assign it a callback function that receives the DOM element as an argument，you can then access the DOM element using the current property of the ref object.</p>
<figure raw="import React, { useRef } from &#x22;react&#x22;

function MyComponent() {
  const ref = useRef(null)
  return <div ref={ref}>Hello, World!</div>
}
" data-language="jsx" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">import </span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">React</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> useRef</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> }</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> from </span><span style="--shiki-light:#EA9D34;--shiki-dark:#F6C177">"react"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> MyComponent</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> useRef</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">null</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">div</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">ref</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">Hello, World!</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">div</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<h2 id="out-of-control-ref"><a href="#out-of-control-ref">Out-of-Control Ref</a></h2>
<p>In react, ref allows you to access the DOM elements of a component directly. This can be useful if you need to manipulate the element's style or if you want to trigger an action based on the element's position in the DOM.</p>
<p>First, look at the case where it is not out of control.</p>
<ul>
<li>Execute <code>ref.current</code>'s focus, blur, etc. methods</li>
<li>Execute <code>ref.current.scrollIntoView</code> to scroll the element into the view</li>
<li>Execute <code>ref.current.getBoundingClientRect</code> to measure the DOM size</li>
</ul>
<p>In these cases, although we manipulate the DOM, they involve factors outside of React's control, so they are not considered out of control.<br>
But in the following cases.</p>
<ul>
<li>Execute <code>ref.current.remove</code> to remove the DOM</li>
<li>Execute <code>ref.current.appendChild</code> to insert a child node</li>
</ul>
<p>These are also DOM operations, but they are within React's control, so performing these operations via ref is out of control.</p>
<p>Here is an example of an out of control situation caused by using Ref to manipulate the DOM.</p>
<figure raw="export default function Counter() {
  const [show, setShow] = useState(true)
  const ref = useRef(null)

  return (
    <div>
      <button
        onClick={() => {
          setShow(!show)
        }}
      >
        Toggle with setState
      </button>
      <button
        onClick={() => {
          ref.current.remove()
        }}
      >
        Remove from the DOM
      </button>
      {show &#x26;&#x26; <p ref={ref}>Hello world</p>}
    </div>
  )
}
" data-language="jsx" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">export default function </span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">Counter</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">[</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">show</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> setShow</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">]</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> = </span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">useState</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">true</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const </span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> = </span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">useRef</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">null</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return </span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">    &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">div</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">button</span></span>
<span data-line=""><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">        onClick</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">          setShow</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">!</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">show</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">        }}</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      ></span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">        Toggle with setState</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">button</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">button</span></span>
<span data-line=""><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">        onClick</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">          ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">current</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">remove</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">        }}</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      ></span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">        Remove from the DOM</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">button</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">      {</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">show</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> &#x26;&#x26;</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">ref</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">Hello world</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">p</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">    &#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">div</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">  )</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>Button 1 removes the P-node by means of React control.<br>
Button 2 removes the P-node by manipulating the DOM directly.</p>
<p>If these two ways of removing P-nodes are mixed, then clicking button 1 and then button 2 will report an error.</p>
<div class="photo-albums" data-total="1"><img src="/blog/craft/static/ref-out-of-control.png" alt="ref out of control" width="1129" height="504"></div>
<p>This is the result of the runaway situation caused by using Ref to manipulate the DOM.</p>
<h2 id="how-to-limit-runaway"><a href="#how-to-limit-runaway">How to limit runaway</a></h2>
<p>Now the question arises, since it is called out of control, it is React can not control, so how to limit the loss of control?</p>
<p>In React, components can be divided into：</p>
<ul>
<li>Low-order components</li>
<li>High-order components</li>
</ul>
<p>Low-order components are those that are wrapped based on the DOM, such as the following components, which are wrapped directly based on input nodes.</p>
<p>In lower-order components, it is possible to point the ref directly to the DOM.</p>
<figure raw="function MyInput(props) {
  const ref = useRef(null)
  return <input ref={ref} {...props} />
}
" data-language="jsx" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> MyInput</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> useRef</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">null</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">input</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">ref</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">...</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> /></span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>Higher-order components are those that are based on lower-order component wrappers, such as the following Form component, based on the Input component wrapper.</p>
<figure raw="function Form() {
  return (
    <>
      <MyInput />
    </>
  )
}
" data-language="jsx" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> Form</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">    &#x3C;></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">MyInput</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> /></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">    &#x3C;/></span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">  )</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>Higher-order components cannot point ref directly to the DOM. This restriction keeps the scope of ref runaway within a single component, and there will be no runaway ref across components.<br>
Take the example in the document, if we want to click a button in the Form component to operate the input focus.</p>
<figure raw="function MyInput(props) {
  return <input {...props} />
}

function Form() {
  const inputRef = useRef(null)

  function handleClick() {
    inputRef.current.focus()
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>input聚焦</button>
    </>
  )
}
" data-language="jsx" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> MyInput</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">input</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">...</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> /></span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> Form</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> inputRef</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> useRef</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">null</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> handleClick</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">    inputRef</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">current</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">focus</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">    &#x3C;></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">MyInput</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">inputRef</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> /></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">button</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> onClick</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">handleClick</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">input聚焦</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">button</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">    &#x3C;/></span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">  )</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>When clicked, an error is reported as follows.</p>
<div class="photo-albums" data-total="1"><img src="/blog/craft/static/input-focus.png" alt="input focus" width="1084" height="448"></div>
<p>This is because passing a ref to MyInput in the Form component fails, inputRef.current does not point to the input node.<br>
The reason for this is that React does not support passing refs across components by default, as mentioned above, in order to keep the scope of refs out of control within a single component.</p>
<h2 id="artificially-removing-the-restriction"><a href="#artificially-removing-the-restriction">Artificially removing the restriction</a></h2>
<p>If you must remove this restriction, you can use the <code>forwardRef</code> API to explicitly pass the ref.</p>
<figure raw="const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />
})

function Form() {
  const inputRef = useRef(null)

  function handleClick() {
    inputRef.current.focus()
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>Focus the input</button>
    </>
  )
}
" data-language="jsx" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> MyInput</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> forwardRef</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">input</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">...</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">ref</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> /></span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> Form</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> inputRef</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> useRef</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">null</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  function</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> handleClick</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">    inputRef</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">current</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">focus</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">    &#x3C;></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">MyInput</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">inputRef</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> /></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">      &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">button</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> onClick</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">handleClick</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">Focus the input</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">&#x3C;/</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">button</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">></span></span>
<span data-line=""><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86">    &#x3C;/></span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">  )</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>With the use of <code>forwardRef</code> it is possible to pass ref across components.In the example, we pass inputRef from Form across components to MyInput and associate it with input.</p>
<p>The intent of <code>forwardRef</code> is clear: since the developer manually calls <code>forwardRef</code> to break the restriction that prevents a runaway ref, he should know what he is doing and should take the corresponding risk himself.</p>
<p>Also, with the presence of <code>forwardRef</code>, it is easier to locate the error after a ref-related error occurs.</p>
<h2 id="useimperativehandle"><a href="#useimperativehandle">useImperativeHandle</a></h2>
<p>In addition to restricting the passing of ref across components, there is another measure to prevent ref from getting out of control, and that is <code>useImperativeHandle</code>.</p>
<p>Since refs get out of control because they use DOM methods that shouldn't be used (like appendChild), I can restrict refs to only have methods that can be used.</p>
<p>Modifying our MyInput component with <code>useImperativeHandle</code>.</p>
<figure raw="const MyInput = forwardRef((props, ref) => {
  const realInputRef = useRef(null)
  useImperativeHandle(ref, () => ({
    focus() {
      realInputRef.current.focus()
    },
  }))
  return <input {...props} ref={realInputRef} />
})
" data-language="jsx" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="jsx" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> MyInput</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> forwardRef</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">(</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">)</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  const</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic"> realInputRef</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97"> useRef</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">null</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">  useImperativeHandle</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">(</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">ref</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">,</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> ()</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0"> =></span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4"> (</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">    focus</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">      realInputRef</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">current</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">focus</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">    },</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  }</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">))</span></span>
<span data-line=""><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">  return</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> &#x3C;</span><span style="--shiki-light:#56949F;--shiki-dark:#9CCFD8">input</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA"> {</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">...</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">props</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#907AA9;--shiki-light-font-style:italic;--shiki-dark:#C4A7E7;--shiki-dark-font-style:italic"> ref</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">=</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">realInputRef</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#9893A5;--shiki-dark:#6E6A86"> /></span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">)</span></span></code></pre></figure>
<p>Now, the Form component can only fetch the following data structure through inputRef.current：</p>
<figure raw="{
  focus() {
    realInputRef.current.focus();
  },
}
" data-language="javascript" data-rehype-pretty-code-figure=""><pre style="--shiki-light:#575279;--shiki-dark:#e0def4;--shiki-light-bg:#faf4ed;--shiki-dark-bg:#232136" tabindex="0" data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon"><code data-language="javascript" data-theme="rose-pine-dawn rose-pine-moon" style="display: grid;"><span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">  focus</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">() </span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">{</span></span>
<span data-line=""><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">    realInputRef</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic">current</span><span style="--shiki-light:#286983;--shiki-dark:#3E8FB0">.</span><span style="--shiki-light:#D7827E;--shiki-dark:#EA9A97">focus</span><span style="--shiki-light:#575279;--shiki-dark:#E0DEF4">()</span><span style="--shiki-light:#797593;--shiki-dark:#908CAA">;</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">  },</span></span>
<span data-line=""><span style="--shiki-light:#797593;--shiki-dark:#908CAA">}</span></span></code></pre></figure>
<p>It eliminates the situation where the developer takes the DOM by ref and then executes the API that should not be used, and the ref gets out of control.</p>
<h2 id="summary"><a href="#summary">Summary</a></h2>
<p>Normally, the use of ref is relatively rare, and he exists as an escape hatch.</p>
<p>To prevent misuse/abuse that leads to ref getting out of control, React restricts that by default, ref cannot be passed across components.</p>
<p>To break this restriction, <code>forwardRef</code> can be used.</p>
<p>To reduce the misuse of ref on DOM, you can use <code>useImperativeHandle</code> to restrict the data structure passed by ref.</p>]]></content:encoded>
            <author>chanshiyucx@gmail.com (Shiyu)</author>
            <category>Web/React</category>
        </item>
    </channel>
</rss>