普通视图

发现新文章,点击刷新页面。
昨天以前四畳半のへや

Tutorial: Create an Astro Component

作者 Typography
2022年7月2日 08:00

If you know HTML, you already know enough to write your first Astro component.

Astro component syntax is a superset of HTML. The syntax was designed to feel familiar to anyone with experience writing HTML or JSX, and adds support for including components and JavaScript expressions.

JSX-like Expressions

You can define local JavaScript variables inside of the frontmatter component script between the two code fences (---) of an Astro component. You can then inject these variables into the component's HTML template using JSX-like expressions!

:::note[dynamic vs reactive] Using this approach, you can include dynamic values that are calculated in the frontmatter. But once included, these values are not reactive and will never change. Astro components are templates that only run once, during the rendering step.

See below for more examples of differences between Astro and JSX. :::

Variables

Local variables can be added into the HTML using the curly braces syntax:

---
const name = 'Astro'
---

<div>
	<h1>Hello {name}!</h1>
	<!-- Outputs <h1>Hello Astro!</h1> -->
</div>

Dynamic Attributes

Local variables can be used in curly braces to pass attribute values to both HTML elements and components:

---
const name = 'Astro'
---

<h1 class={name}>Attribute expressions are supported</h1>

<MyComponent templateLiteralNameAttribute={`MyNameIs${name}`} />

:::caution HTML attributes will be converted to strings, so it is not possible to pass functions and objects to HTML elements. For example, you can't assign an event handler to an HTML element in an Astro component:

---
// dont-do-this.astro
function handleClick() {
	console.log('button clicked!')
}
---

<!-- ❌ This doesn't work! ❌ -->
<button onClick={handleClick}>Nothing will happen when you click me!</button>

Instead, use a client-side script to add the event handler, like you would in vanilla JavaScript:

---
// do-this-instead.astro
---

<button id='button'>Click Me</button>
<script>
	function handleClick() {
		console.log('button clicked!')
	}
	document.getElementById('button').addEventListener('click', handleClick)
</script>

My App Defaults 2023

作者 Moeyua
2023年12月29日 01:16

最近在 Another Dayu 这里发现了 App Defaults 这个有趣的项目,而我从来没法抗拒这种类型的分享,所以这就是我的 App Defaults:

一些补充和吐槽

RSS

我依然希望每一个人都能使用 RSS 来订阅自己喜欢的内容,而不是被推送算法所控制。

Browser

在这之前我一直在使用 Chrome,但是 Arc 的体验让我不得不改变这个选择。我很欣赏 Arc 对于侧边栏的尝试,而且优秀的 UI 也让人爱不释手。基于 Chromium 的 Arc 也让我不用担心开发过程中的问题。我很期待 Arc 的未来。

Bookmarks

我一直在使用 GitHub Gist 来存储我的书签,原因有以下几点:

  • 能在链接后增加一些必要的描述,防止自己想不起来这是做什么的/为什么收藏;
  • 能够精简浏览器收藏夹,快速找到常用的内容;
  • 可以通过插件的形式对本页面内容进行快速搜索;
  • 经过简单拓展能够转变为「个人知识库」。
  • 可以向其他人共享。

Budgeting and Personal Finance

我很赞同 Percento “「记帐」的根本目的是「管账」” 的理念,只更新重要的财务变动将「记账」这件事的负担降到了最低,而且 Percento 的设计也非常好看。

Surge

从去年开始我就陆续将自己的科学上网工具转向了 Surge。Surge 在 iOS 和 macOS 的协同体验非常棒,而且由于 Surge 定位是 Network Toolbox,这使得 Surge 也能完成一些简单的网络管理功能。今年 tvOS 开放了网络拓展功能后 Surge 也能够在 Apple TV 上使用,让我的看剧体验更加完美。

Code Assistant

我不敢想没了 Github Copilot 我还怎么写代码。

最后

我很期待看到更多人的 App Defaults,如果你也有兴趣,可以在 App Defaults 这里找到更多信息。

读书的意义

作者 Moeyua
2023年4月27日 06:07

朋友在世界读书日看到一个名为《对你影响最大的书是什么》的话题的时候:

感觉虽然这些年读的比前些年多 但是得到的东西都是很碎片化的东西 看到这种话题我需要回想一下

看书的影响很大程度上是潜移默化的,短时间根本看不出来。一本书看完大概只有其中的几个片段能留在脑海里,大部分内容在看的时候就已经改变了你的思考方式。

跟何况「影响最大」本身就难讲,是指态度?思想方式?还是现实生活呢。昨晚在书店和女朋友正好聊到漫画,我说“我最喜欢的漫画应该是知音漫客上的《暴走邻家》,如果没看这个漫画,我现在八成还不知道是哪里的混混呢”。那这么看对我影响最大的书就是一本不太知名的二流漫画,或者也可能是《ES6标准入门》,但从现实角度考虑肯定不会是陈嘉映。

书嘛,看就看了,也不用非要追求什么意义。非要说有什么意义的话,大概是能让我滑手机的时间更少一点。

从零开始的 RSSHub Docker 私有化部署指南

作者 Moeyua
2022年6月28日 03:16

使用 RSSHub 也已经很长一段时间了,慢慢萌生了尝试一下自己部署 RSSHub 的想法,能够进行一些自定义的内容,也可以解决许多国内网站反爬严格的问题。部署过程还算顺利,这里做一个记录。

安装 Docker

  1. 安装 yum-utils

    sudo yum install -y yum-utils
    
    sudo yum-config-manager \
        --add-repo \
        https://download.docker.com/linux/centos/docker-ce.repo
    
    
  2. 安装 Docker Engine

    sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin
    
  3. 启动 Docker:

    sudo systemctl start docker
    
  4. 确认 Docker 安装正确:

    sudo docker run hello-world
    

    这个命令下载了一个测试镜像,并在一个容器中运行它。当容器运行时,它会打印一条信息并退出。

  5. 设置 Docker 开机启动

    sudo systemctl enable docker
    
安装 Docker Compose
  1. 下载 Docker Compose 的当前稳定版本:

sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose


2. 将可执行权限应用于二进制文件:

```bash
sudo chmod +x /usr/local/bin/docker-compose
  1. 检查版本:

    docker-compose --version
    

部署 RSSHub

  1. 下载 docker-compose.yml

    wget https://raw.githubusercontent.com/DIYgod/RSSHub/master/docker-compose.yml
    
  2. 检查有无需要修改的配置

    vi docker-compose.yml
    
  3. 创建 volume 持久化 Redis 缓存

    docker volume create redis-data
    
  4. 启动

    docker-compose up -d
    

开放服务器端口

docker-compose.yml 文件中配置的端口号默认为 1200,需要在服务器上开启对应的端口号,使用时只需要将 https://rsshub.app/twitter/user/evodmoeyua 中的 https://rsshub.app 替换为 http://yourip:1200

【译文】Grid 用于布局, Flexbox 用于组件

作者 Moeyua
2022年3月5日 22:27

本文是 Ahmad Shadeed 的博客文章 Grid for layout, Flexbox for components 的翻译。

3 月 5 号开始翻译,摸了 3 个月终于翻译完了,下次还敢(不是


我的弟弟是一名刚毕业的软件工程师,现在他正在做前端开发相关的实习岗位。他以前学过 Grid 和 flexbox,但是我发现和我经常网上看到的情况一样,他在布局的时候使用 Grid 还是 flexbox 之间摇摆不定。举个例子,他尝试使用 Grid 布局去开发一个网站的 header,但是当他使用了 grid-column 属性的时候他发现过程好像并不像想象中那么顺利,所以他只能不停地调整来让页面看起来和设计稿一致。

说句实话,我不太喜欢这样子,所以我试着去找一些关于这方面的资料来让他了解 grid 和 flexbox 之间的区别,最好还能带上几个例子,但可惜的是我一无所获。所以我尝试写了一篇涵盖了这个主题所有内容的文章,希望大家能够从中获益。

介绍

在深入探讨概念和实例之前,首先要确保你了解 CSS grid 和 flexbox 的主要区别。CSS Grid是一个拥有「行」和「列」概念的多维布局模块。Flexbox 可以将其子项布局成列或行,但不能同时进行(译者注:可以理解为一维)。

如果你还不了解关于 CSS grid 和 flexbox 相关的知识,我非常建议你去阅读这篇 可视化文章。如果你已经了解了这些内容那就太棒了,接下来我们就将深入了解它们直接的区别,以及如何使用它们。

Grid 和 Flexbox 之间的不同点

首先我需要澄清一点,关于「何时使用 CSS Grid 或 flexbox」这里没有一个非常明确的界线。还有一点就是,没有「使用这种方法 正确 或者 错误」这种说法。这篇文章是推荐在特定的使用情况下使用某种技术的指南,我将会讲述一些基本概念,然后通过一些例子说明这些概念,剩余的就需要靠你自己去探索和实验了。

/* Flexbox 容器 */
.wrapper {
  display: flex;
}

/* Grid 容器 */
.wrapper {
  display: grid;
  grid-template-columns: 2fr 1fr;
  grid-gap: 16px;
}

grid-vs-flexbox

发现了什么吗?Flexbox 是在一行内布局自己的元素,CSS grid 使其转化为拥有行和列的表格。Flexbox 是在行内进行对齐的,当然如果我们愿意也可以在列内。

/* Flexbox 容器 */
.wrapper {
  display: flex;
  flex-direction: column;
}

grid-vs-flexbox-1

如何决定使用哪种布局呢

在 CSS grid 和 flexbox 之间做决定有时会很困难,特别是刚入门 CSS 的新手,我非常理解这种心情,我在开始选择之前会问自己下面这几个问题:

  • 组件内的元素是如何展示的?行内还是行和列?

  • 组件在不同种类的屏幕大小下期望的展示方式是什么?

如果组件的子元素的排列方式是行内,那大多数情况最好的方案应该就是 flexbox 了。看看下面这个例子:

decide-1

如果是行和列的话,CSS grid 应该是这种情况下的方案。

decide-2

现在我已经说明了它们之间主要的不同点,现在让我们看一下更加特殊的例子来学习如何决定这两种布局。

一些案例和实例

在下面的章节中,我将详细讨论 flexbox 和 grid 的不同使用情况。

CSS Grid
两栏式布局(Main And Sidebar)

当你需要一个两栏式布局(一个侧边栏和主要内容)时,CSS grid 就是最好的选择,请看下面这个例子:

grid-use-1

这里是实现代码:

<div class="wrapper">
  <aside>Sidebar</aside>
  <main>Main</main>
</div>
@media (min-width: 800px) {
  .wrapper {
    display: grid;
    grid-template-columns: 200px 1fr;
    grid-gap: 16px;
  }
  aside {
    align-self: start;
  }
}

如果 <aside> 元素没有使用 align-self 属性,那么它的高度就会无视内容,始终等于 <main> 元素。

Cards Grid(网格卡片)

正如我们在文章开头所说的,布局 cards grid 的最佳方式从 CSS grid 的名字就不言而喻了。

grid-use-2

这里是我实现这个布局的代码:

.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-gap: 16px;
}

每一列的宽度最小应该是 200px,如果没有足够的空间则会自动换到下一行。需要注意的是,如果视口(viewport)的宽度小于 200px ,上述写法会导致在水平方向上发生滚动。

一个比较简单的解决方式是只有在 viewport 的宽度足够时上述代码才会生效,像下面这样:

@media (min-width: 800px) {
  .wrapper {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    grid-gap: 16px;
  }
}
Section Layout

在下面的这个例子中,我们可以使用两次 grid 布局来实现。首先我们使用 grid 布局将整个区域分成两个部分(侧边栏部分和表单部分),接着我们就可以使用 grid 将表单进行布局。

grid-use-3

I can’t emphasis how CSS grid is perfect for that. 下面是 CSS 实现代码:

@media (min-width: 800px) {
  .wrapper {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
  
  .form-wrapper {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-gap: 16px;
  }
  
  .form-message,
  .form-button {
    grid-column: 1 /3; /* 让它们充满整个宽度 */
  }
}

这个例子引用自我在 Envato 的 这一篇文章,这是一篇关于在 web 开发时如何使用 CSS grid 进行布局的文章,非常值得一读。

CSS Flexbox
网站导航

在 90% 的情况下,网站的导航栏都应该使用 CSS flexbox 进行开发。它们最大的共同之处就是在左边有一个 logo,导航的部分都在右边。这种情况非常适用于 flexbox。

flexbox-use-1

要想实现上面这个例子,你只需要按照下面的方式进行设置:

.site-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

同样的,上述代码也可以用在下面的这种设计中。

flexbox-use-2

你应该注意到导航栏的结构发生了一点变化,但是子元素之间的间距仍由 ustify-content 属性决定。

操作列表

当你听到列表的时候第一反应一定是垂直排列的列表。这里特别说明一下,一个列表也可以在一行内排列,所以各位不要搞错。

关于操作列表的例子我们可以参考 Facebook 或者 Twitter。操作列表由几个操作按钮组成。我们看看下面的这张截图吧:

flexbox-use-3

就像你看到的那样,所有元素之间彼此相邻,而且它们水平分布。Flexbox 简直是最完美的选择,这也是它的核心用途之一。

.action-list {
  display: flex;
}

.action-list_item {
  flex: 1; /* 让所有子元素扩大,使得它们能够平分所有空间 */
}

另一个类似的用法就是用作弹窗的标题和操作按钮。 不管弹窗页脚 footer 还是页眉 header 的子元素全都是在行内排列,就像你看到的,他们之间的空隙如下所示: flexbox-use-4 对于弹窗的页眉来说,下面这种写法就能够满足需要:

.modal-header {
	display: flex;
	justify-content: space-between;
}

页脚对于我们来说可能有一些不同的地方。「取消」按钮需要将 margin 设置为 auto 来将它放置到右边。关于这一点我写了一篇详细介绍这方面内容的 [文章](https://ishadeed.com/article/auto-css/ 。

.cancel_action {
	margin-left: auto;
}

.cancel_action 可能在这里不是一个好的命名方式,但是在这篇文章里我并不打算详细说明有关 CSS 的命名规则。

表单元素

一个输入框旁边紧挨着一个按钮的组合可以说是 Flexbox 的完美案例了。 请看下面这个例子:

flexbox-use-5

在第一个表单当中,输入框占据了所有的剩余空间,所以我们需要给它设置一个动态的宽度。同样的,第二个表单(Facebook 的 Messenger)也是一样,文字输入框占据了所有的剩余位置。让我们试着模仿一下。

flexbox-use-6

.input {
  flex: 1 1 auto;
}

主要注意的是,如果我们没有在文字输入框上设置 flex: 1 1 auto,那么输入框就不会自动填充满整个剩余空间。

跟帖回复

Flexbox 的另一个比较通用的例子就是「跟帖回复」,比如下面这个例子:

flexbox-use-7

这里有用户的头像和评论本身,评论模块占据了容器的所有剩余空间,用 flexbox 可以完美的实现这种布局。

卡片组件

卡片组件的种类有非常多,但是一般来说最常见的还是下面例子中的这种设计:

flexbox-use-8

左边的例子中,我们将 flex 的方向设置为了 column,所以卡片的子元素是叠在一起的。相反,右边的方向是 row ,而且 flexbox 默认的方向就是 row,这一点千万不要忘记。

.card {
	display: flex;
	flex-direction: column;
}
@media (min-width: 800px) {
	.card {
		flex-direction: row;
	}
}

另外一个比较常见的变种是在卡片中有一个图标,而且会有一个文字标签紧挨在下面。它可能是一个按钮、一个链接、或者就仅仅是装饰而已。让我们看看下面这个例子:

flexbox-use-9

值得注意的是图标和文本标签在水平和垂直方向是居中的。感谢 flexbox,让我们能够很简单的实现这种布局。

.card {
  display: flex;
  flex-direction: column;
  align-items: center;
}

行内的样式是默认的,我们只需要删除 flex-direction: column 让它恢复到默认的值 <row> 就可以了。

标签页 / 底部菜单

当一个元素的宽度需要始终等于屏幕宽度,而且它的子元素需要填满所有的可用空间,这时 flexbox 就是我们的最佳选择。

flexbox-use-10

在上面这个例子中,所有的子元素都应该充满可用空间,而且所有子元素的宽度相同。我们只需要将容器的 display 设置为 flex 就可以很轻松的实现这一布局了。

.tabs_item {
  flex-grow: 1;
}

这种方式被 React Native 框架用来在移动端创建 Tab 菜单。这里有一段 React Native 的示例代码展示了和上面一样的内容。这里的代码是从 这里 借鉴来的。

import React from "react";
import { View } from "react-native";

export default FlexDirectionBasics = () => {
  return (
    <View style=>
      <View
        style=
      />
      <View
        style=
      />
      <View
        style=
      />
    </View>
  );
};
功能列表

关于 flexbox 最喜欢的一点就是它能够随意翻转元素的方向。flexbox 默认的方向是 row ,但是我们可以像下面这样转换一下:

.item {
	flex-direction: row-reverse;
}

在下面这个例子中我们能看到偶数项被翻转,这就是通过上面的方式实现的,非常实用。

flexbox-use-11

居中内容

然后我们设想这样一种情况:我们有一个很重要的部分,这个部分的内容需要被垂直且水平居中。水平居中我们能够使用文本对齐简单的实现。

flexbox-use-12

.hero {
  text-align: center;
}

但是如何使用 flexbox 将元素垂直居中呢?这正是我们需要实现的:

.hero {
	display: flex;
	flex-direction: column;
	align-items: center; /* 水平居中元素 */
	justify-content: center; /* 垂直居中元素 */
	text-align: center;
}

CSS Grid 和 Flexbox 的结合

不仅每个布局模块有自己的使用例,我们甚至可以将他们结合起来使用。当我考虑如何将这两种布局方式结合在一起的时候,我脑海中的第一个想法就是卡片列表。使用 Grid 来布局卡片,使用 flexbox 来布局卡片组件自身。

grid-and-flex

上面的这个布局有以下几点需求:

  • 每一行的卡片高度应该保持相等;

  • 不管卡片高度如何,Read more 按钮应该始终在卡片的底部;

  • Grid 应当使用 minimal() 函数。

<div class="wrapper">
  <article class="card">
    <img src="sunrise.jpg" alt="" />
    <div class="card__content">
      <h2><!-- Title --></h2>
      <p><!-- Desc --></p>
      <p class="card_link"><a href="#">Read more</a></p>
    </div>
  </article>
</div>
@media (min-width: 500px) {
  .wrapper {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-gap: 16px;
  }
}

.card {
  display: flex; /* [1] */
  flex-direction: column; /* [2] */
}

.card__content {
  flex-grow: 1; /* [3] */
  display: flex; /* [4] */
  flex-direction: column;
}

.card__link {
  margin-top: auto; /* [5] */
}

现在让我来解释一下上面的 CSS。

  1. 将卡片设置成 flexbox 容器
  2. 排列方向为纵向,也就是说卡片的元素是通过栈的方式进入的
  3. 让卡片的内容扩大并充满所有剩余空间
  4. 将卡片内容设置为 flexbox 容器
  5. 最后,使用 margin-top: auto 将 链接 推入栈中。这样不管卡片高度是多少它都会保持在最底部。

正如你所看到的,结合 CSS grid 和 flexbox 并不是很困难。这两种方式可以给我们带来许多种实现 web 布局的方式。我们应该正确的使用它们,而且只在像上述需要的情况去结合它们。

支持旧版本的浏览器的回退方案

通过 CSS @supports

大概在几个月之前,我得到了一条推特回复说我的网站在 IE11 上崩溃了。在检查过后我发现了一个奇怪的现象。所有的网站内容都被压缩都了左上角的区域。我的网站无法使用了!

ishadeed-ie11

是的,这就是我的网站——一个前端开发工程师的网站,在 IE11 上。首先让我感到困惑的是,这是如何发生的?我记得 CSS grid 是支持 IE11 的,但是这是微软发布的旧版本。解决方法非常简单,那就是使用 @supports 只在新的浏览器中使用 CSS grid。

@supports (grid-area: auto) {
  body {
    display: grid;
  }
}

让我解释一下。我使用 grid-area 是因为它只在新的 CSS grid 规范中被支持,从2017年3月到今天。由于IE不支持 @supports 查询,整个规则将被忽略。因此,新的CSS网格将只用于支持的浏览器。

使用 Flexbox 作为 CSS Grid 的回退方案

flexbox 不适合用于展示网格布局中的项目,但是这并不意味着它不能当作备用方案。你可以使用 flexbox 来作为 不支持 CSS grid 浏览器的备用方案。我曾经开发了一个工具l用来解决这个问题。

@mixin grid() {
  display: flex;
  flex-wrap: wrap;

  @supports (grid-area: auto) {
    display: grid;
    grid-gap: 16px 16px;
  }
}

@mixin gridAuto() {
  margin-left: -16px;

  > * {
    margin-bottom: 16px;
    margin-left: 16px;
  }

  @media (min-width: 320px) {
    > * {
      width: calc((99% / #{2}) - 16px);
      flex: 0 0 calc((99% / #{2}) - 16px);
    }
  }

  @media (min-width: 768px) {
    > * {
      width: calc((99% / #{3}) - 16px);
      flex: 0 0 calc((99% / #{3}) - 16px);
    }
  }

  @supports (grid-area: auto) {
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    margin-left: 0;

    > * {
      width: auto;
      margin-left: 0;
      margin-bottom: 0;
    }
  }
}

上面的回退代码通过下面所述的方式运行:

  1. 添加 display: flexflex-wrap: wrap 使元素换行;
  2. 检查 CSS grid 是否被支持,如果支持,那么就会使用 display: grid
  3. 通过使用 > *选择器,我们可以选择容器的直接子后代。在选择完成后,我们就可以给它们每一个都添加一个特殊的宽度或者大小了。
  4. 当然,它们之间的间隙是必须的,如果浏览器支持 CSS grid,我们将会用 grid-gap 来充当间隙。

这里是一个使用 Sass mixin 的例子:

.wrapper {
  @include grid();
  @include gridAuto();
}

Demo

如果 Grid 和 Flexbox 都无法正常使用

当我和我弟弟在进行代码评审的时候,我注意到了几个 CSS grid 和 flexbox 都会错误的使用方式,我认为将它们作为重点写出来很有必要。

使用 CSS Grid 来布局网站头部区域

这个问题是让我写下这篇文章的动机之一。我注意到我的弟弟使用 CSS grid 来实现网站的头部区域。

他辩解道“CSS grid 实在是太复杂,太难了“等等。由于使用了不正确的布局方法,他得到了一个想法,认为这是 CSS grid 太复杂造成的。其实不然,他所有的困惑都来自于把它用在不合适的地方的事实。

看一下我注意到的这个例子:

incorrect-use-1

.site-header {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
}

.site-nav {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
}

CSS grid 被使用了两次,第一次是用于整个标题,第二次是用于导航。他用 grid-column 来微调元素之间的间距,还有其他一些奇怪的东西,我记不起来了,但是最重要的你应该明白了吧!

使用 CSS Grid 在标签上

CSS grid 的另一个不正确的用法是将其应用于标签组件。请看下面的模拟图。

incorrect-use-2

下面是错误的 CSS 代码:

.tabs-wrapper {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

从上面的代码中,我可以看到,开发者假设标签数只有三个。因此,他用 1fr 1fr 1fr 来布置列。如果列数发生变化,布局就失效了。

过度使用 Flexbox 或者 Grid

记住,旧的布局方法可能是完美的方式。过度使用 flexbox 或 grid 会使你的 CSS 的复杂程度随着时间而增加。我并不是说它们很复杂,而是像本文中的例子所解释的那样,在正确的情况下正确地使用它们会好很多。

例如,你有如下的主要部分,要求将其所有内容水平居中。

img

我们可以通过 text-align: center 来实现这种布局,那么这个时候我们为什么要去使用 felxbox 这种更复杂的方式?

总结

关于使用 CSS Grid 和 Flexbox 之间的区别,我们已经说了很多了。这个话题我想了很久,我很高兴有机会写这个话题。如果有任何问题请不要犹豫,通过电子邮件或twitter @shadeed9 提供反馈!

感谢你的阅读!

【译文】IndexedDB 为什么这么慢?如何更好的使用呢?

作者 Moeyua
2022年1月15日 06:48

本文是 RxDB 文档 Opinions 部分的文章 Why IndexedDB is slow and what to use instead 的翻译,原作者为 pubkey


我们可能出于离线使用的需求,也可能是出于缓存等等的其他目的,需要将 JavaScript Web Application 的数据保存在客户端本地也就是浏览器里。而在浏览器内存储数据一般来说有以下几个选项:

  • Cookies 会随着每次 HTTP 请求被发送出去,所以它不能存储太多的数据。
  • WebSQL 已经被 弃用,因为它从来都不是一个标准,而将它变成标准又十分困难。
  • LocalStorage 是一个基于异步 IO-access 的同步 API,存储和读取都会使得 JavaScript 进程被完全阻塞,所以不应该在有许多键值对的情况下使用 LocalStorage 。
  • FileSystem API 可以用来存储简单的二进制文件,但可惜现在 只有 Chrome 支持 这一特性。
  • IndexedDB 是一种「键-值数据库」,可以用来存储 json 类型的数据,而且能够遍历所有的索引。IndexedDB 不仅稳定,而且得到 广泛的支持

不难看出,最好的选择就是 IndexedDB。选定了要使用的方法后就可以着手进行开发了。在刚开始的时候似乎一切都很不错,但随着进度的推进你的应用程序也越来越大,你需要处理更多或者更复杂的数据,这时你发现事情并没有那么简单—— IndexedDB 太慢了,甚至比运行在廉价服务器上的数据库还要慢!插入几百个文档就要花费好几秒的时间。对于一个需要快速加载的页面来说时间是至关重要的,有时候直接向后端发送请求来传输数据都要比 IndexedDB 要快。

事务处理 vs 吞吐量

在抱怨之前我们可以先分析一下这么慢的原因。当你在 Nolans 的 浏览器数据库比较 中测试时就能发现:插入 1k 条文档到 IndexedDB 大概会花费 80ms,平均 0.08ms 一条,不仅不算慢,甚至可以称得上很快了,而且我们也不太可能同时在客户端存储如此大量的数据。但问题的关键在于这些文档是在 single transaction 上写入的。

所以我 fork 了一个 对比工具 并将每次写入文档的方式修改为 single transaction,我们可以看到 single transaction 插入 1k 条文档大概花费 2 秒钟。但有趣的是,当我们把文档的大小增加到原来的 100 倍以后,存储这些数据的时间差不多和原来是一样的!这下我们大概就清楚了原来限制 IndexedDB 性能的是 transaction 而不是数据吞吐量。

IndexedDB transaction throughput

要想解决 IndexedDB 的性能问题你可以尽可能使用更少的 transactions 。有的时候很容易就能解决这个问题:使用 RxDB 的 bulk 方法 你就可以一次性将许多数据压缩并存储。但是大多数情况下事情没有这么简单:你的用户不停的在页面上点击,重复的数据不停的从后端发送过来,另外一个页面同时还在写入数据。所有的这些事情都会在你不知道的什么时候发生,你也不可能将这些数据全都在单个 transactions 处理完成。

另一个解决的办法就是不要再去关心它的性能问题。一些浏览器厂商将会对 IndexedDB 进行优化,它的速度将会有所改观。当然,IndexedDB 缓慢的问题在 2013 年 就已经有了,按照这种趋势,我们有理由相信在未来几年它也还是会缓慢下去,所以我们不应该等待下去。chromium 的开发者们也发布了一个 声明 来呼吁大家更多的关注读取性能而不是写入性能。

使用 WebSQL (即便它已经被弃用)也不是一个很好的选择,因为就像 对比工具显示的结果 一样,它的 transactions 甚至更慢。

不要将 IndexedDB 当作数据库来使用

为了处理性能问题和防止 transaction ,我们应当停止将 IndexedDB 当作数据库来使用。相反的,我们应当在初始页面加载时就将所有数据载入到内存(memory)当中。这样一来所有的读写操作都在内存当中进行,而内存的读写速度是原来的 100 倍。只有在数据写入以后,通过单事务写入的方式将内存状态持久化到 IndexedDB 中。这种情况下 Indexed 是作为一个文件系统来使用的,而不是一个数据库。

这里有一些已经采用了这种方式的库:

持久化

不直接使用 IndexedDB 的一个缺点就是你的数据不会一直持久化。当 JavaScript 进程在你还没有持久化到 IndexedDB 之前就退出的话,你的数据很有可能就丢失了。为了防止这种情况发生,我们必须要确保内存中的数据已经被写入到了硬盘。一个很重要的点就是尽可能快的将数据进行持久化。例如 LokiJS 就提供了 incremental-indexeddb-adapter 用来持久化最新的数据到硬盘当中而不是每次都去持久化所有的数据。而另外一点就是要在正确的时间去持久化你的数据。例如 RxDB LokiJS storage 只会在以下几种情况持久化数据:

  • 当有新的数据写入出现,而此时数据库处于空闲状态,既没有读取也没有写入在进行。此时会对数据进行持久化。
  • window 触发了 beforeunload event 的时候,说明此时的 JavaScript 进程随时都可能退出,这时一定要进行数据持久化。在 beforeunload 结束之后会有大概几秒的时间让我们足够保存所有新的改动,这足以证明我们的工作是可靠的。

唯一遗漏的地方就是如果浏览器突然崩溃或者电脑电源被关闭,这会导致浏览器意外退出。

多标签支持

Web application 和 「普通」应用程序的最大不同就在于用户可以同时在多个浏览器标签当中使用你的应用。但是假如你的所有内存中的数据库状态只会定期写入硬盘,多个浏览器标签可能会发生冲突造成数据丢失。但是这可能对于依赖于服务端响应的应用程序不是什么问题,因为丢失的数据可能早已经上传到了后端,其他标签的数据也一样。但是如果你的客户端是离线使用的话这样就行不通了。

解决这个问题最理想的方法就是使用 SharedWorker。SharedWorker 和 WebWorker 一样都运行在单独的 JavaScript 进程中,唯一不同的是 SharedWorker 会在多个上下文见进行共享。你可以在 SharedWorker 中创建数据库,这样所有的浏览器标签就会向 Worker 请求数据而不是去单独创建一个数据库。但很遗憾的是 SharedWorker API 并不支持所有浏览器。Safari 放弃了对它的支持,而 IE 和 安卓平台的 Chrome 甚至从来都没有适配过。

此外,我们可以通过 BroadcastChannel API 在标签页之间进行通信,然后在它们之间采用 leader election。Leader election 能够确保无论有多少个标签页被打开永远都会有一个标签是 Leader

Leader Election

Leader election 的缺点就是它的进程会在首页面加载时消耗一定时间(大概 150 毫秒)。此外,当 JavaScript 进程阻塞的时候 Leader election 可能会被中断。当这种情况发生时,一个好的解决办法就是重新加载浏览器的标签使 election 进程重新启动。

RxDB LokiJS Storage 已经实现了 Leader election 这种方法来支持多标签。

延伸阅读

Hello 2022

作者 Moeyua
2021年12月31日 22:28

2021 对我来说是前所未有的一年,太多事情影响了我,能够很明显感觉到自己的改变,在很多事情上有了更加清晰的认识。希望在即将到来的 2022 年,不管是自己的文字也好,写的代码也好,还是我的一些想法也好,都能给他人带来一点帮助和启发,让这个似乎已经很糟糕的世界变得更好,这应该就是每一个理想主义者的宿命罢。

「言論」 零壹

作者 Moeyua
2021年11月20日 20:51

分类的时候一定会出现的两个问题,一个是会出现不属于任何一个分类的文件,另一个就是会出现同属于多个类别的文件,所以我认为分类的最好方式就是不分类,再好的分类方案都比不上顺手且快速的搜索方式。


所谓的「知识管理」只是一种手段,而不是目的。如果你本身没有需要解决的问题(或者说专注研究的领域),那么知识管理只是个伪命题。


输入:尽量多的捕捉下来自己的想法和知识盲区,但避免无脑摘录。 输出:重要的不是文采,而是让自己内化知识,并获得高质量的反馈。


任何一个好的系统都不应该耗费你大量的时间去维护,一旦你需要不断地「定期」维护一个系统,那么就违背了系统的初衷 —— 整理东西本身并不能产生太大的价值,除了耗费时间。

hexo 无法在本地实时预览

作者 Moeyua
2021年11月17日 01:24

自从更换了 icarus 主题以后,之前一直在使用的 hexo s --debug 以及 hexo s 都没有办法在本地实时对页面进行更新,只能通过 hexo ghexo s 的方式重新启动服务器才能够更新。虽然这样也能看到预览,但是像我这种写一下看一眼的选手来说这可是要命的,属实是困扰了我好久。

<!-- more -->

今天摸鱼时候寻思不如赶紧解决掉,查了一圈,虽然不知道是什么原因导致的,但解决办法找到了:

hexo g --watch

Hexo 能够监视文件变动并立即重新生成静态文件,在生成时会比对文件的 SHA1 checksum,只有变动的文件才会写入。

上面是 hexo 官方给出的命令解释,也就是说虽然 hexo s 不能够帮我们监视文件变化,那么我们就自己来监视。只需要在 hexo s 之前启动一个 hexo g --watch 就能解决这个问题了。

<a class="tag is-dark is-medium" href="https://www.pixiv.net/artworks/94130143" target="_blank"> <span class="icon"><i class="fas fa-camera"></i></span>   Cover by あんよ@お仕事募集中 </a>

解决 nvm 无法在 arm 架构下安装 V15 以下的 node 版本 的问题

作者 Moeyua
2021年11月9日 02:08

迫于需要维护公司一个比较老的项目,所以在配置 macOS 环境的时候选择了使用 nvm 来管理多个 node,但是遇到了一些问题。 <!-- more --> 根据 nvm 官方文档的说法:

January 2021: there are no pre-compiled NodeJS binaries for versions prior to 15.x for Apple's new M1 chip (arm64 architecture).

也就是说 M1 芯片( arm64 )现在并没有对应的预编译版本,所以安装之后需要进行编译。而在编译过程中会遇到一些问题:

  • 编译成功,但是因为内存不足而崩溃( crashes ),增加足够的 node 内存后再次尝试但依然提示内存不足;
  • 直接编译失败。

这里我遇到的是第二种情况,也就是直接编译失败。那么如何解决这个问题呢, nvm 其实在文档里给出了一个方案,这个方案有两个前提:

  • 使用 zsh
  • 已经安装好 Rosetta 2 macOS 应该在 macOS X 上的默认终端就已经是 zsh 了,而 Rosetta 2 如果在你第一次打开因特尔架构的软件时就已经安装过了,如果没有安装过也可以手动进行安装:
softwareupdate --install-rosetta

以上两个条件都满足之后我们就可以处理这个问题了。

  1. 首先检查自己的 node 架构,返回的结果应该是 arm64,这个是 M1 芯片的架构,也就是我们问题的元凶
node -p process.arch

# arm64
  1. 在 64 位 x86 架构下启动一个新的 zsh 进程;
arch -x86_64 zsh
  1. 下载你需要的 node 版本,这个 node 将会是 x86 架构的;
nvm install node
  1. 现在检查一下架构是否正确;
node -p process.arch

# x64
  1. 退出这个进程。
exit

到这里我们就成功的安装好了一个低版本的 node。

使用 RSS 在推荐算法中获取主动权

作者 Moeyua
2021年1月31日 05:25

前言

当学习或者工作感到疲惫时,我们通常会放下手里的事情,拿起手机休息一下,这倒也无可厚非,但是今天我们似乎很难自己掌控休息的时间,往往拿起手机就被各种内容吸引,各类软件依靠着自己的推荐算法,总是能带来新鲜的信息,但是这些信息并不能带给我太多东西,比起零碎的信息还是系统性的的知识比较有用。

<!--more-->

为了让我们回归工作和学习,解决信息过载,获取接受信息的主动权,RSS 或许是一个很好的选择,我们可以通过 RSS 主动选择和调整订阅源,摆脱推荐算法,让获取信息变的简单。

鉴于熟练RSS需要一定的学习,这篇文章就想详细讲讲什么是 RSS ,如何使用 RSS 在网络中获取主动权,争取让小白也可以看懂。

什么是RSS

RSS 其实并不是什么新鲜的技术,相反 RSS 是在被成为 WEB1.0 时代就已经出现的技术,我们来看一下维基百科上面对 RSS 的定义:

RSS(全称:RDF Site Summary;Really Simple Syndication),中文译作「简易信息聚合」,也称「聚合内容」,是一种消息来源 格式规范,用以聚合经常发布更新资料的网站,例如 博客 文章、新闻、音频 或 视频 的网摘。RSS 文件(或称做摘要、网络摘要、或频更新,提供到频道)包含全文或是节录的文字,再加上发布者所订阅之网摘资料和授权的元数据。简单来说,RSS 能够让用户订阅个人网站个人博客,当订阅的网站有新文章是能够获得通知。

Really Simple Syndication「简易信息聚合」,听名字就能知道这是一个十分简单的技术,简单来说 RSS最主要的目的就是给个人网站和博客提供信息聚合,并通知所有订阅的阅读者,使信息能够更高效的传播。

RSS 能做什么

  • 可以看到没有广告和图片的标题或文章的摘要,这样你不必阅读全文即可知文章讲的一个意思是什么,方便确定是否要阅读本文。

  • RSS 阅读器会自动更新你定制的网站内容,保持新闻的及时性。这样每天你就可以在固定时间打开 RSS 阅读最新文章,而不必打开各个软件和网站,也不会被其他消息所干扰。

  • 使用 RSS 可以根据你自已的喜好定制多个 RSS 订阅源,这样做的好处是从多个网站来源搜集,然后整合到单个数据流当中。

  • 可以在 RSS 中的路由参数中选择订阅网站的某个栏目,比如知乎热榜,微博热搜,而不需要订阅整个网站。

  • 某些阅读器甚至提供了过滤功能,用户只需要提供关键词,阅读器就会自动过滤掉相应内容,极大的简化了我们的信息流

RSS怎么使用

RSS的使用方式实际上很简单,找到一个订阅源,然后添加到RSS阅读器当中刷新就能完成订阅,但实际情况却比这个麻烦很多————因为你并没有订阅源。

要想知道一个网站是否支持RSS,最直接的方法就是看网站的底部或侧边栏是否有 RSS 图标。一般来说,图标所指向的地址就是该网站的订阅链接,你可以直接点击 RSS 订阅链接跳转到 RSS 客户端内进行订阅,也可以复制粘贴按钮中的地址到自己在用的 RSS 服务中订阅这些网站中的内容。

以我的 博客 为例,上方导航栏的 RSS 即为订阅链接,复制或点击即可进行订阅,移动端可以长按图标复制订阅源。


是复制链接而不是链接里的内容!!

RSSHub

现在我们已经知道如何添加订阅源了,但是你会发现很多主流网站像微博,哔哩哔哩,知乎等并没有支持 RSS,那么这时候就需要介绍一个开源项目————RSSHub

RSSHub 是一个开源、简单易用、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源。RSSHub 借助于开源社区的力量快速发展中,目前已适配数百家网站的上千项内容

以上内容来自RSSHub的官网介绍,就像官网所说的一样,通过RSSHub确实能够做到 万物皆可 RSS ,你可以通过 https://docs.rsshub.app/ 来访问他们的官网,里面由很详细的文档以及许多RSS路由,这些RSS几乎涵盖了生活中涉及到的所有方面,所以我们就不需要自己去各个网站寻找RSS图标了


接下来我就以微博为例展示如何使用RSSHub

注意路由部分
这里的路由中没有带”:” 表示为固定内容,不需要更改,而带有冒号的表示为参数,需要使用者自行配置。这里有 uid 和 routeParams 两个参数,其中第一个参数为必选参数,第二个参数为可选择参数,后者可以根据个人需求进行配置,这里只展示第一个参数 uid:

  1. 首先我们打开博主主页,按下F12启动控制台
  2. 切换到控制台( console )选项卡
  3. 在一堆提示最下面执行 $CONFIG.oid
  4. 得到的数字就是我们需要的参数 uid
  5. 用我们得到的 id 替换路由中的 :uid

这样我们就能够得到博主“游点艺术”微博的RSS
https://rsshub.app/weibo/user/2040839563

接下来只需要复制这个订阅源,到阅读器中订阅,我们就可以在阅读器中接收到博主的微博了。

RSSHub 还支持自建路由和浏览器插件,感兴趣的可以参考官方文档。

RSS 阅读器

RSS 阅读器我个人使用过 Inoreader 以及 ios 端的 Reeder4,目前在使用的是 Reeder4。

Reeder4

官网:https://reederapp.com/

不支持中文界面

Reeder 是 iOS 和 Mac 的老牌阅读器了,说是最好的阅读器也不过分,它除了可以让你手动加入 RSS 频道外,也可以从 Feedbin,Feedly,Inoreader,The Old Reader 等 RSS 阅读平台导入数据,程序本身支持 iCloud Reader 稍后,Pocket,Instapaper 等稍后阅读服务。在 iOS 平台手势操作也是一大亮点,标题左划 Star 文章,右划标记为已读,文章页左划查看源网站,几乎所有操作都可以用手势完成。

Inoreader

官网:https://www.inoreader.com/

支持中文界面

就功能而言 Inoreader 更加丰富,直观的界面设计,高级模式甚至支持去重功能,避免当热点新闻发生时避免被大量内容重复的文章刷屏,也支持关键词过滤,增强订阅源等等。免费版的功能也足够非重度用户使用,且支持 Win,Android,iOS,Mac 以及浏览器使用。推荐新手入门使用

推荐订阅源

https://moeyua.notion.site/RSS-1eda6578a8cf474eb9d0634821119334

这里是我个人使用的部分订阅源,是当作笔记写在Notion上的,因为并没有什么内容,暂时不打算搬到博客上来,有需要可以自行查看。

参考资料

  1. 论 RSS 的「复兴」
  2. RSS-Wikipedia
  3. Web 源与内容聚合: RSS/Atom 的扩展、生成、发布、发现与共享
  4. RSSHub
  5. 高效获取信息,你需要这份 RSS 入门指南

读书的意义

作者 Moeyua
2023年4月26日 22:07

朋友在世界读书日看到一个名为《对你影响最大的书是什么》的话题的时候:

感觉虽然这些年读的比前些年多
但是得到的东西都是很碎片化的东西
看到这种话题我需要回想一下

看书的影响很大程度上是潜移默化的,短时间根本看不出来。一本书看完大概只有其中的几个片段能留在脑海里,大部分内容在看的时候就已经改变了你的思考方式。

跟何况「影响最大」本身就难讲,是指态度?思想方式?还是现实生活呢。昨晚在书店和女朋友正好聊到漫画,我说“我最喜欢的漫画应该是知音漫客上的《暴走邻家》,如果没看这个漫画,我现在八成还不知道是哪里的混混呢”。那这么看对我影响最大的书就是一本不太知名的二流漫画,或者也可能是《ES6标准入门》,但从现实角度考虑肯定不会是陈嘉映。

书嘛,看就看了,也不用非要追求什么意义。非要说有什么意义的话,大概是能让我滑手机的时间更少一点。

【译文】Grid 用于布局, Flexbox 用于组件

作者 Moeyua
2022年3月5日 14:27

本文是 Ahmad Shadeed 的博客文章 Grid for layout, Flexbox for components 的翻译。

3 月 5 号开始翻译,摸了 3 个月终于翻译完了,下次还敢(不是


我的弟弟是一名刚毕业的软件工程师,现在他正在做前端开发相关的实习岗位。他以前学过 Grid 和 flexbox,但是我发现和我经常网上看到的情况一样,他在布局的时候使用 Grid 还是 flexbox 之间摇摆不定。举个例子,他尝试使用 Grid 布局去开发一个网站的 header,但是当他使用了 grid-column 属性的时候他发现过程好像并不像想象中那么顺利,所以他只能不停地调整来让页面看起来和设计稿一致。

说句实话,我不太喜欢这样子,所以我试着去找一些关于这方面的资料来让他了解 grid 和 flexbox 之间的区别,最好还能带上几个例子,但可惜的是我一无所获。所以我尝试写了一篇涵盖了这个主题所有内容的文章,希望大家能够从中获益。

介绍

在深入探讨概念和实例之前,首先要确保你了解 CSS grid 和 flexbox 的主要区别。CSS Grid是一个拥有「行」和「列」概念的多维布局模块。Flexbox 可以将其子项布局成列或行,但不能同时进行(译者注:可以理解为一维)。

如果你还不了解关于 CSS grid 和 flexbox 相关的知识,我非常建议你去阅读这篇 可视化文章。如果你已经了解了这些内容那就太棒了,接下来我们就将深入了解它们直接的区别,以及如何使用它们。

Grid 和 Flexbox 之间的不同点

首先我需要澄清一点,关于「何时使用 CSS Grid 或 flexbox」这里没有一个非常明确的界线。还有一点就是,没有「使用这种方法 正确 或者 错误」这种说法。这篇文章是推荐在特定的使用情况下使用某种技术的指南,我将会讲述一些基本概念,然后通过一些例子说明这些概念,剩余的就需要靠你自己去探索和实验了。

/* Flexbox 容器 */.wrapper {  display: flex;}/* Grid 容器 */.wrapper {  display: grid;  grid-template-columns: 2fr 1fr;  grid-gap: 16px;}

grid-vs-flexbox

发现了什么吗?Flexbox 是在一行内布局自己的元素,CSS grid 使其转化为拥有行和列的表格。Flexbox 是在行内进行对齐的,当然如果我们愿意也可以在列内。

/* Flexbox 容器 */.wrapper {  display: flex;  flex-direction: column;}

grid-vs-flexbox-1

如何决定使用哪种布局呢

在 CSS grid 和 flexbox 之间做决定有时会很困难,特别是刚入门 CSS 的新手,我非常理解这种心情,我在开始选择之前会问自己下面这几个问题:

  • 组件内的元素是如何展示的?行内还是行和列?

  • 组件在不同种类的屏幕大小下期望的展示方式是什么?

如果组件的子元素的排列方式是行内,那大多数情况最好的方案应该就是 flexbox 了。看看下面这个例子:

decide-1

如果是行和列的话,CSS grid 应该是这种情况下的方案。

decide-2

现在我已经说明了它们之间主要的不同点,现在让我们看一下更加特殊的例子来学习如何决定这两种布局。

一些案例和实例

在下面的章节中,我将详细讨论 flexbox 和 grid 的不同使用情况。

CSS Grid
两栏式布局(Main And Sidebar)

当你需要一个两栏式布局(一个侧边栏和主要内容)时,CSS grid 就是最好的选择,请看下面这个例子:

grid-use-1

这里是实现代码:

<div class="wrapper">  <aside>Sidebar</aside>  <main>Main</main></div>
@media (min-width: 800px) {  .wrapper {    display: grid;    grid-template-columns: 200px 1fr;    grid-gap: 16px;  }  aside {    align-self: start;  }}

如果 <aside> 元素没有使用 align-self 属性,那么它的高度就会无视内容,始终等于 <main> 元素。

Cards Grid(网格卡片)

正如我们在文章开头所说的,布局 cards grid 的最佳方式从 CSS grid 的名字就不言而喻了。

grid-use-2

这里是我实现这个布局的代码:

.wrapper {  display: grid;  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));  grid-gap: 16px;}

每一列的宽度最小应该是 200px,如果没有足够的空间则会自动换到下一行。需要注意的是,如果视口(viewport)的宽度小于 200px ,上述写法会导致在水平方向上发生滚动。

一个比较简单的解决方式是只有在 viewport 的宽度足够时上述代码才会生效,像下面这样:

@media (min-width: 800px) {  .wrapper {    display: grid;    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));    grid-gap: 16px;  }}
Section Layout

在下面的这个例子中,我们可以使用两次 grid 布局来实现。首先我们使用 grid 布局将整个区域分成两个部分(侧边栏部分和表单部分),接着我们就可以使用 grid 将表单进行布局。

grid-use-3

I can’t emphasis how CSS grid is perfect for that. 下面是 CSS 实现代码:

@media (min-width: 800px) {  .wrapper {    display: grid;    grid-template-columns: 200px 1fr;  }    .form-wrapper {    display: grid;    grid-template-columns: 1fr 1fr;    grid-gap: 16px;  }    .form-message,  .form-button {    grid-column: 1 /3; /* 让它们充满整个宽度 */  }}

这个例子引用自我在 Envato 的 这一篇文章,这是一篇关于在 web 开发时如何使用 CSS grid 进行布局的文章,非常值得一读。

CSS Flexbox
网站导航

在 90% 的情况下,网站的导航栏都应该使用 CSS flexbox 进行开发。它们最大的共同之处就是在左边有一个 logo,导航的部分都在右边。这种情况非常适用于 flexbox。

flexbox-use-1

要想实现上面这个例子,你只需要按照下面的方式进行设置:

.site-header {  display: flex;  flex-wrap: wrap;  justify-content: space-between;}

同样的,上述代码也可以用在下面的这种设计中。

flexbox-use-2

你应该注意到导航栏的结构发生了一点变化,但是子元素之间的间距仍由 ustify-content 属性决定。

操作列表

当你听到列表的时候第一反应一定是垂直排列的列表。这里特别说明一下,一个列表也可以在一行内排列,所以各位不要搞错。

关于操作列表的例子我们可以参考 Facebook 或者 Twitter。操作列表由几个操作按钮组成。我们看看下面的这张截图吧:

flexbox-use-3

就像你看到的那样,所有元素之间彼此相邻,而且它们水平分布。Flexbox 简直是最完美的选择,这也是它的核心用途之一。

.action-list {  display: flex;}.action-list_item {  flex: 1; /* 让所有子元素扩大,使得它们能够平分所有空间 */}

另一个类似的用法就是用作弹窗的标题和操作按钮。
不管弹窗页脚 footer 还是页眉 header 的子元素全都是在行内排列,就像你看到的,他们之间的空隙如下所示:
flexbox-use-4
对于弹窗的页眉来说,下面这种写法就能够满足需要:

.modal-header {display: flex;justify-content: space-between;}

页脚对于我们来说可能有一些不同的地方。「取消」按钮需要将 margin 设置为 auto 来将它放置到右边。关于这一点我写了一篇详细介绍这方面内容的 [文章](https://ishadeed.com/article/auto-css/

.cancel_action {margin-left: auto;}

.cancel_action 可能在这里不是一个好的命名方式,但是在这篇文章里我并不打算详细说明有关 CSS 的命名规则。

表单元素

一个输入框旁边紧挨着一个按钮的组合可以说是 Flexbox 的完美案例了。
请看下面这个例子:

flexbox-use-5

在第一个表单当中,输入框占据了所有的剩余空间,所以我们需要给它设置一个动态的宽度。同样的,第二个表单(Facebook 的 Messenger)也是一样,文字输入框占据了所有的剩余位置。让我们试着模仿一下。

flexbox-use-6

.input {  flex: 1 1 auto;}

主要注意的是,如果我们没有在文字输入框上设置 flex: 1 1 auto,那么输入框就不会自动填充满整个剩余空间。

跟帖回复

Flexbox 的另一个比较通用的例子就是「跟帖回复」,比如下面这个例子:

flexbox-use-7

这里有用户的头像和评论本身,评论模块占据了容器的所有剩余空间,用 flexbox 可以完美的实现这种布局。

卡片组件

卡片组件的种类有非常多,但是一般来说最常见的还是下面例子中的这种设计:

flexbox-use-8

左边的例子中,我们将 flex 的方向设置为了 column,所以卡片的子元素是叠在一起的。相反,右边的方向是 row ,而且 flexbox 默认的方向就是 row,这一点千万不要忘记。

.card {display: flex;flex-direction: column;}@media (min-width: 800px) {.card {flex-direction: row;}}

另外一个比较常见的变种是在卡片中有一个图标,而且会有一个文字标签紧挨在下面。它可能是一个按钮、一个链接、或者就仅仅是装饰而已。让我们看看下面这个例子:

flexbox-use-9

值得注意的是图标和文本标签在水平和垂直方向是居中的。感谢 flexbox,让我们能够很简单的实现这种布局。

.card {  display: flex;  flex-direction: column;  align-items: center;}

行内的样式是默认的,我们只需要删除 flex-direction: column 让它恢复到默认的值 <row> 就可以了。

标签页 / 底部菜单

当一个元素的宽度需要始终等于屏幕宽度,而且它的子元素需要填满所有的可用空间,这时 flexbox 就是我们的最佳选择。

flexbox-use-10

在上面这个例子中,所有的子元素都应该充满可用空间,而且所有子元素的宽度相同。我们只需要将容器的 display 设置为 flex 就可以很轻松的实现这一布局了。

.tabs_item {  flex-grow: 1;}

这种方式被 React Native 框架用来在移动端创建 Tab 菜单。这里有一段 React Native 的示例代码展示了和上面一样的内容。这里的代码是从 这里 借鉴来的。

import React from "react";import { View } from "react-native";export default FlexDirectionBasics = () => {  return (    <View style=>      <View        style=      />      <View        style=      />      <View        style=      />    </View>  );};
功能列表

关于 flexbox 最喜欢的一点就是它能够随意翻转元素的方向。flexbox 默认的方向是 row ,但是我们可以像下面这样转换一下:

.item {flex-direction: row-reverse;}

在下面这个例子中我们能看到偶数项被翻转,这就是通过上面的方式实现的,非常实用。

flexbox-use-11

居中内容

然后我们设想这样一种情况:我们有一个很重要的部分,这个部分的内容需要被垂直且水平居中。水平居中我们能够使用文本对齐简单的实现。

flexbox-use-12

.hero {  text-align: center;}

但是如何使用 flexbox 将元素垂直居中呢?这正是我们需要实现的:

.hero {display: flex;flex-direction: column;align-items: center; /* 水平居中元素 */justify-content: center; /* 垂直居中元素 */text-align: center;}

CSS Grid 和 Flexbox 的结合

不仅每个布局模块有自己的使用例,我们甚至可以将他们结合起来使用。当我考虑如何将这两种布局方式结合在一起的时候,我脑海中的第一个想法就是卡片列表。使用 Grid 来布局卡片,使用 flexbox 来布局卡片组件自身。

grid-and-flex

上面的这个布局有以下几点需求:

  • 每一行的卡片高度应该保持相等;

  • 不管卡片高度如何,Read more 按钮应该始终在卡片的底部;

  • Grid 应当使用 minimal() 函数。

<div class="wrapper">  <article class="card">    <img src="sunrise.jpg" alt="" />    <div class="card__content">      <h2><!-- Title --></h2>      <p><!-- Desc --></p>      <p class="card_link"><a href="#">Read more</a></p>    </div>  </article></div>
@media (min-width: 500px) {  .wrapper {    display: grid;    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));    grid-gap: 16px;  }}.card {  display: flex; /* [1] */  flex-direction: column; /* [2] */}.card__content {  flex-grow: 1; /* [3] */  display: flex; /* [4] */  flex-direction: column;}.card__link {  margin-top: auto; /* [5] */}

现在让我来解释一下上面的 CSS。

  1. 将卡片设置成 flexbox 容器
  2. 排列方向为纵向,也就是说卡片的元素是通过栈的方式进入的
  3. 让卡片的内容扩大并充满所有剩余空间
  4. 将卡片内容设置为 flexbox 容器
  5. 最后,使用 margin-top: auto 将 链接 推入栈中。这样不管卡片高度是多少它都会保持在最底部。

正如你所看到的,结合 CSS grid 和 flexbox 并不是很困难。这两种方式可以给我们带来许多种实现 web 布局的方式。我们应该正确的使用它们,而且只在像上述需要的情况去结合它们。

支持旧版本的浏览器的回退方案

通过 CSS @supports

大概在几个月之前,我得到了一条推特回复说我的网站在 IE11 上崩溃了。在检查过后我发现了一个奇怪的现象。所有的网站内容都被压缩都了左上角的区域。我的网站无法使用了!

ishadeed-ie11

是的,这就是我的网站——一个前端开发工程师的网站,在 IE11 上。首先让我感到困惑的是,这是如何发生的?我记得 CSS grid 是支持 IE11 的,但是这是微软发布的旧版本。解决方法非常简单,那就是使用 @supports 只在新的浏览器中使用 CSS grid。

@supports (grid-area: auto) {  body {    display: grid;  }}

让我解释一下。我使用 grid-area 是因为它只在新的 CSS grid 规范中被支持,从2017年3月到今天。由于IE不支持 @supports 查询,整个规则将被忽略。因此,新的CSS网格将只用于支持的浏览器。

使用 Flexbox 作为 CSS Grid 的回退方案

flexbox 不适合用于展示网格布局中的项目,但是这并不意味着它不能当作备用方案。你可以使用 flexbox 来作为 不支持 CSS grid 浏览器的备用方案。我曾经开发了一个工具l用来解决这个问题。

@mixin grid() {  display: flex;  flex-wrap: wrap;  @supports (grid-area: auto) {    display: grid;    grid-gap: 16px 16px;  }}@mixin gridAuto() {  margin-left: -16px;  > * {    margin-bottom: 16px;    margin-left: 16px;  }  @media (min-width: 320px) {    > * {      width: calc((99% / #{2}) - 16px);      flex: 0 0 calc((99% / #{2}) - 16px);    }  }  @media (min-width: 768px) {    > * {      width: calc((99% / #{3}) - 16px);      flex: 0 0 calc((99% / #{3}) - 16px);    }  }  @supports (grid-area: auto) {    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));    margin-left: 0;    > * {      width: auto;      margin-left: 0;      margin-bottom: 0;    }  }}

上面的回退代码通过下面所述的方式运行:

  1. 添加 display: flexflex-wrap: wrap 使元素换行;
  2. 检查 CSS grid 是否被支持,如果支持,那么就会使用 display: grid
  3. 通过使用 > *选择器,我们可以选择容器的直接子后代。在选择完成后,我们就可以给它们每一个都添加一个特殊的宽度或者大小了。
  4. 当然,它们之间的间隙是必须的,如果浏览器支持 CSS grid,我们将会用 grid-gap 来充当间隙。

这里是一个使用 Sass mixin 的例子:

.wrapper {  @include grid();  @include gridAuto();}

Demo

如果 Grid 和 Flexbox 都无法正常使用

当我和我弟弟在进行代码评审的时候,我注意到了几个 CSS grid 和 flexbox 都会错误的使用方式,我认为将它们作为重点写出来很有必要。

使用 CSS Grid 来布局网站头部区域

这个问题是让我写下这篇文章的动机之一。我注意到我的弟弟使用 CSS grid 来实现网站的头部区域。

他辩解道“CSS grid 实在是太复杂,太难了“等等。由于使用了不正确的布局方法,他得到了一个想法,认为这是 CSS grid 太复杂造成的。其实不然,他所有的困惑都来自于把它用在不合适的地方的事实。

看一下我注意到的这个例子:

incorrect-use-1

.site-header {  display: grid;  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;}.site-nav {  display: grid;  grid-template-columns: 1fr 1fr 1fr 1fr;}

CSS grid 被使用了两次,第一次是用于整个标题,第二次是用于导航。他用 grid-column 来微调元素之间的间距,还有其他一些奇怪的东西,我记不起来了,但是最重要的你应该明白了吧!

使用 CSS Grid 在标签上

CSS grid 的另一个不正确的用法是将其应用于标签组件。请看下面的模拟图。

incorrect-use-2

下面是错误的 CSS 代码:

.tabs-wrapper {  display: grid;  grid-template-columns: 1fr 1fr 1fr;}

从上面的代码中,我可以看到,开发者假设标签数只有三个。因此,他用 1fr 1fr 1fr 来布置列。如果列数发生变化,布局就失效了。

过度使用 Flexbox 或者 Grid

记住,旧的布局方法可能是完美的方式。过度使用 flexbox 或 grid 会使你的 CSS 的复杂程度随着时间而增加。我并不是说它们很复杂,而是像本文中的例子所解释的那样,在正确的情况下正确地使用它们会好很多。

例如,你有如下的主要部分,要求将其所有内容水平居中。

img

我们可以通过 text-align: center 来实现这种布局,那么这个时候我们为什么要去使用 felxbox 这种更复杂的方式?

总结

关于使用 CSS Grid 和 Flexbox 之间的区别,我们已经说了很多了。这个话题我想了很久,我很高兴有机会写这个话题。如果有任何问题请不要犹豫,通过电子邮件或twitter @shadeed9 提供反馈!

感谢你的阅读!

【译文】IndexedDB 为什么这么慢?如何更好的使用呢?

作者 Moeyua
2022年1月14日 22:48

本文是 RxDB 文档 Opinions 部分的文章 Why IndexedDB is slow and what to use instead 的翻译,原作者为 pubkey


我们可能出于离线使用的需求,也可能是出于缓存等等的其他目的,需要将 JavaScript Web Application 的数据保存在客户端本地也就是浏览器里。而在浏览器内存储数据一般来说有以下几个选项:

  • Cookies 会随着每次 HTTP 请求被发送出去,所以它不能存储太多的数据。
  • WebSQL 已经被 弃用,因为它从来都不是一个标准,而将它变成标准又十分困难。
  • LocalStorage 是一个基于异步 IO-access 的同步 API,存储和读取都会使得 JavaScript 进程被完全阻塞,所以不应该在有许多键值对的情况下使用 LocalStorage 。
  • FileSystem API 可以用来存储简单的二进制文件,但可惜现在 只有 Chrome 支持 这一特性。
  • IndexedDB 是一种「键-值数据库」,可以用来存储 json 类型的数据,而且能够遍历所有的索引。IndexedDB 不仅稳定,而且得到 广泛的支持

不难看出,最好的选择就是 IndexedDB。选定了要使用的方法后就可以着手进行开发了。在刚开始的时候似乎一切都很不错,但随着进度的推进你的应用程序也越来越大,你需要处理更多或者更复杂的数据,这时你发现事情并没有那么简单—— IndexedDB 太慢了,甚至比运行在廉价服务器上的数据库还要慢!插入几百个文档就要花费好几秒的时间。对于一个需要快速加载的页面来说时间是至关重要的,有时候直接向后端发送请求来传输数据都要比 IndexedDB 要快。

事务处理 vs 吞吐量

在抱怨之前我们可以先分析一下这么慢的原因。当你在 Nolans 的 浏览器数据库比较 中测试时就能发现:插入 1k 条文档到 IndexedDB 大概会花费 80ms,平均 0.08ms 一条,不仅不算慢,甚至可以称得上很快了,而且我们也不太可能同时在客户端存储如此大量的数据。但问题的关键在于这些文档是在 single transaction 上写入的。

所以我 fork 了一个 对比工具 并将每次写入文档的方式修改为 single transaction,我们可以看到 single transaction 插入 1k 条文档大概花费 2 秒钟。但有趣的是,当我们把文档的大小增加到原来的 100 倍以后,存储这些数据的时间差不多和原来是一样的!这下我们大概就清楚了原来限制 IndexedDB 性能的是 transaction 而不是数据吞吐量。

IndexedDB transaction throughput

要想解决 IndexedDB 的性能问题你可以尽可能使用更少的 transactions 。有的时候很容易就能解决这个问题:使用 RxDB 的 bulk 方法 你就可以一次性将许多数据压缩并存储。但是大多数情况下事情没有这么简单:你的用户不停的在页面上点击,重复的数据不停的从后端发送过来,另外一个页面同时还在写入数据。所有的这些事情都会在你不知道的什么时候发生,你也不可能将这些数据全都在单个 transactions 处理完成。

另一个解决的办法就是不要再去关心它的性能问题。一些浏览器厂商将会对 IndexedDB 进行优化,它的速度将会有所改观。当然,IndexedDB 缓慢的问题在 2013 年 就已经有了,按照这种趋势,我们有理由相信在未来几年它也还是会缓慢下去,所以我们不应该等待下去。chromium 的开发者们也发布了一个 声明 来呼吁大家更多的关注读取性能而不是写入性能。

使用 WebSQL (即便它已经被弃用)也不是一个很好的选择,因为就像 对比工具显示的结果 一样,它的 transactions 甚至更慢。

不要将 IndexedDB 当作数据库来使用

为了处理性能问题和防止 transaction ,我们应当停止将 IndexedDB 当作数据库来使用。相反的,我们应当在初始页面加载时就将所有数据载入到内存(memory)当中。这样一来所有的读写操作都在内存当中进行,而内存的读写速度是原来的 100 倍。只有在数据写入以后,通过单事务写入的方式将内存状态持久化到 IndexedDB 中。这种情况下 Indexed 是作为一个文件系统来使用的,而不是一个数据库。

这里有一些已经采用了这种方式的库:

持久化

不直接使用 IndexedDB 的一个缺点就是你的数据不会一直持久化。当 JavaScript 进程在你还没有持久化到 IndexedDB 之前就退出的话,你的数据很有可能就丢失了。为了防止这种情况发生,我们必须要确保内存中的数据已经被写入到了硬盘。一个很重要的点就是尽可能快的将数据进行持久化。例如 LokiJS 就提供了 incremental-indexeddb-adapter 用来持久化最新的数据到硬盘当中而不是每次都去持久化所有的数据。而另外一点就是要在正确的时间去持久化你的数据。例如 RxDB LokiJS storage 只会在以下几种情况持久化数据:

  • 当有新的数据写入出现,而此时数据库处于空闲状态,既没有读取也没有写入在进行。此时会对数据进行持久化。
  • window 触发了 beforeunload event 的时候,说明此时的 JavaScript 进程随时都可能退出,这时一定要进行数据持久化。在 beforeunload 结束之后会有大概几秒的时间让我们足够保存所有新的改动,这足以证明我们的工作是可靠的。

唯一遗漏的地方就是如果浏览器突然崩溃或者电脑电源被关闭,这会导致浏览器意外退出。

多标签支持

Web application 和 「普通」应用程序的最大不同就在于用户可以同时在多个浏览器标签当中使用你的应用。但是假如你的所有内存中的数据库状态只会定期写入硬盘,多个浏览器标签可能会发生冲突造成数据丢失。但是这可能对于依赖于服务端响应的应用程序不是什么问题,因为丢失的数据可能早已经上传到了后端,其他标签的数据也一样。但是如果你的客户端是离线使用的话这样就行不通了。

解决这个问题最理想的方法就是使用 SharedWorker。SharedWorker 和 WebWorker 一样都运行在单独的 JavaScript 进程中,唯一不同的是 SharedWorker 会在多个上下文见进行共享。你可以在 SharedWorker 中创建数据库,这样所有的浏览器标签就会向 Worker 请求数据而不是去单独创建一个数据库。但很遗憾的是 SharedWorker API 并不支持所有浏览器。Safari 放弃了对它的支持,而 IE 和 安卓平台的 Chrome 甚至从来都没有适配过。

此外,我们可以通过 BroadcastChannel API 在标签页之间进行通信,然后在它们之间采用 leader election。Leader election 能够确保无论有多少个标签页被打开永远都会有一个标签是 Leader

Leader Election

Leader election 的缺点就是它的进程会在首页面加载时消耗一定时间(大概 150 毫秒)。此外,当 JavaScript 进程阻塞的时候 Leader election 可能会被中断。当这种情况发生时,一个好的解决办法就是重新加载浏览器的标签使 election 进程重新启动。

RxDB LokiJS Storage 已经实现了 Leader election 这种方法来支持多标签。

延伸阅读

Hello 2022

作者 Moeyua
2021年12月31日 14:28

2021 对我来说是前所未有的一年,太多事情影响了我,能够很明显感觉到自己的改变,在很多事情上有了更加清晰的认识。希望在即将到来的 2022 年,不管是自己的文字也好,写的代码也好,还是我的一些想法也好,都能给他人带来一点帮助和启发,让这个似乎已经很糟糕的世界变得更好,这应该就是每一个理想主义者的宿命罢。

如何在 JavaScript 完美的确定一个数据的类型

作者 Moeyua
2021年11月3日 19:02

JavaScript 中有三种方式来确定一个数据的类型:

  • typeof 运算符
  • instanceof 运算符
  • Object.prototype.toString() 方法
    这里就来简单梳理一下这三种方式的优劣,同时得出一个能够完美判断数据类型的方法。

typeof

typeof 很简单,下面是一个简单的例子:

let a = 'foo';let b = 1;let c = true;typeof a    // "string"typeof b    // "number"typeof c    // "boolean"

使用 typeof 判断『原始类型』(数值、字符串、布尔值)时分别返回 numberstringboolean。『合成类型』(对象、数组、函数)分别返回 objectobjectfunction

let a = {foo = 'bar'};let b = ['foo', 'bar'];let c = function(foo) {    return foo + 'bar'}typeof a    // "object"typeof b    // "object"typeof c    // "function"

而对于 undefinednull 这两种类型,typeof 能够正常判断 undefined,但是 typeof null 会返回 object

let a = undefined;let b = null;typeof a    // "undefined"typeof b    // "object"

出现这种情况的原因时最初版的 JavaScript 并没有将 null 单独拿出来作为一个数据类型,只是作为 object 类型的一种,后来将 null 单独拿了出来,但是为了兼容以前的旧的代码,就没有改变 typeof null 的返回值。

可见 typeof 大部分情况下确实能够准确判断出数据类型,但是对于 Arraynull 就会失效。

instanceof

instanceof 运算符返回一个布尔值,表示对象是否为某个构造函数的实例。根据这一特点我们就能够使用该运算符判断数据类型。

let a = {foo = 'bar'};let b = ['foo', 'bar'];let c = function(){};a instanceof Object     // trueb instanceof Array      // truec instanceof Function   // true

如上面的代码所示,instanceof 能够区分对象的各种类型,但是需要注意的是,instanceof 只能判断对象类型,对于 numberstringbooleanundefinednull 这几种类型是无法判断的。

Object.prototype.toString()

Object.prototype.toString() 方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。不同数据类型的 toString 方法返回如下

数据类型 返回值
数字 [object Number]
字符串 [object String]
布尔值 [object Boolean]
数组 [object Array]
函数 [object Function]
undefined [object Undefined]
null [object Null]
arguments 对象 [object Arguments]
Error 对象 [object Error]
Date 对象 [object Date]
RegExp 对象 [object RegExp]
其他对象 [object Object]

那么基于这一特性,我们可以近乎完美的确定一个常见数据的类型:

var type = function (o){  var s = Object.prototype.toString.call(o);  return s.match(/\[object (.*?)\]/)[1].toLowerCase();};type({});// "object"type([]); // "array"type(5); // "number"type(null); // "null"type(); // "undefined"type(/abcd/); // "regex"type(new Date()); // "date"

需要注意的是,由于实例对象可能会改写 Object.prototype.toSring,所以我们直接使用 Object.prototype.toSring,使用函数的 Call 方法在任意值上调用这个方法。

在上面这个type函数的基础上,还可以加上专门判断某种类型数据的方法。

var type = function (o){  var s = Object.prototype.toString.call(o);  return s.match(/\[object (.*?)\]/)[1].toLowerCase();};['Null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp'].forEach(function (t) {  type['is' + t] = function (o) {    return type(o) === t.toLowerCase();  };});type.isObject({}) // truetype.isNumber(NaN) // truetype.isRegExp(/abc/) // true

Cookie?小饼干!

作者 Moeyua
2021年11月2日 15:09

虽然在浏览网页的时候经常看到 Cookie 这个词汇,但是好像几乎没有人知道是做什么的呢。

概述

Cookie,又称“小甜饼”,指某些网站为了辨别用户身份而储存在用户本地终端(通常为用户的浏览器)上的数据(通常经过加密)。

简单来讲,Cookie 是由服务器保存在用户浏览器上的一小块数据,而且每次都会和 HTTP 请求一起发送给服务器。通常 Cookie 的作用有大概三种:

  • 会话状态管理(用户登陆状态、购物车数据)
  • 个性化设置(颜色、字体、字号等其他自定义设置)
  • 浏览器行为跟踪(跟踪并分析用户行为)

Cookie 这个名字应该源自一种叫 Fortune Cookie 的饼干,这种饼干里面包有写着一些有趣的句子的纸条。它这种内里包含有隐藏的信息的寓意被用在了计算机上。用户发送给服务器的每一次请求都携带有用户的一些信息,所以就用 Cookie 来指代这些很小的信息碎片。

由于 HTTP 请求是没有状态的,服务器无法知道用户在上一次请求时做了什么,甚至也不知道这个用户是谁,这个特性给用户带来了很糟糕的体验,也其实严重阻碍了 交互式Web应用程序 的实现。Cookie 的作用就是在每次请求时携带一些必要的信息告诉服务器,这次请求的发送者是谁;同时因为 Cookie 保存在本地,有些数据直接从本地读取就可以,例如一些个性化设置。

Cookie 不是一种理想的客户端存储机制。它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端存储建议使用 Web storage API 和 IndexedDB。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie 里面。

每个 Cookie 都有以下几方面的元数据。

  • Cookie 的名字
  • Cookie 的值(真正的数据写在这里面)
  • 到期时间(超过这个时间会失效)
  • 所属域名(默认为当前域名)
  • 生效的路径(默认为当前网址)

我们以简单的用户登陆为例,用户登陆网站,浏览器向服务器发送请求,服务器回应的头信息告诉浏览器设置一个 Cookie,这个 Cookie 保存了服务端识别用户所需要的信息。之后用户的每一次请求都会将 Cookie 一同发送给服务器,然后服务器根据不同的用户返回相应的数据。但是 Cookie 并不会一直存在,在最开始设置的时候它的生效时间就以及定好了,所以在 Cookie 过期后,用户发现网站提示登陆失效,需要重新登陆,这时用户就需要重新登陆,而服务器会再次告诉浏览器设置一个 Cookie 以识别用户,直到它再次失效。以下是这个流程的一个示意图:

创建和发送 Cookie

服务器如果希望在浏览器保存 Cookie,就要在 HTTP 回应的头信息里面,放置一个或者 Set-Cookie 字段用来生成一个或多个 Cookie。
除了 Cookie 的值,Set-Cookie 字段还可以附加多个 Cookie ,没有次序的要求。

HTTP/1.0 200 OKContent-type: text/htmlSet-Cookie: yummy_cookie=chocoSet-Cookie: tasty_cookie=strawberrySet-Cookie: <cookie-name>=<cookie-value>; Expires=<date>Set-Cookie: <cookie-name>=<cookie-value>;Max-Age=<non-zero-digit>Set-Cookie: <cookie-name>=<cookie-value>;Domain=<domain-value>Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>Set-Cookie: <cookie-name>=<cookie-value>; SecureSet-Cookie: <cookie-name>=<cookie-value>; HttpOnly

Cookie 的生命周期

上文我们说过 Cookie 不会一直保存,所以在设置时需要定义 Cookie 的生命周期。Cookie 的生命周期分为两种:

  • 会话期 Cookie 不需要定义 ExpiresMax-Age 在浏览器关闭时就会结束生命周期,所以会话期 Cookie 仅在会话期间有效。需要注意的是有些浏览器提供了会话恢复的功能,这将会导致 Cookie 在浏览器重新启动后依然存在,使得 Cookie 一直存在,这可能会导致一些问题。
  • 持久性 Cookie 的生命周期取决于 ExpiresMax-Age 所设定的时间。

如果您的站点对用户进行身份验证,则每当用户进行身份验证时,它都应重新生成并重新发送会话 Cookie,甚至是已经存在的会话 Cookie。此技术有助于防止会话固定攻击(session fixation attacks) (en-US),在该攻击中第三方可以重用用户的会话。

如果服务器想改变一个早先设置的 Cookie,必须同时满足四个条件:Cookie 的 keydomainpathsecure 都匹配,否则都会修改失败,浏览器就会生成一个全新的 Cookie,而不是替换掉原来那个 Cookie。

发送 Cookie

浏览器在发送请求时候也会带上相应的 Cookie,下面是一个例子:

GET /sample_page.html HTTP/1.1Host: www.example.orgCookie: yummy_cookie=choco; tasty_cookie=strawberry

Cookie 的属性

Expires, Max-Age

Expires 属性指定一个 UTC 格式的时间作为 Cookie 的过期时间,当时间超过以后 Cookie 就会被删除。但是需要注意的是该时间以本地时间为准,所以并不能保证一定会在指定的时间被删除。
Max-Age 指定 Cookie 会在创建后的多少秒后被删除,倒计时结束后该 Cookie 就会被删除。

需要注意的是 ExpiresMax-Age 如果都没有设置或者有效的值的时候该 Cookie 就成为了 Session Cookie,在结束会话的时候就会被删除。
如果 ExpiresMax-Age 同时都被设置时以后者为准,也就是说 Max-Age 的优先级更高。

Domain, Path, SameSite

DomainPath 定义了 Cookie 的作用域,也就是 Cookie 在哪些网站有效。
Domain 设置一个域名作为 Cookie 的作用域,如果没有设置则浏览器默认为当前所在的域名。

需要特别注意的是,通过 Domain 设置的作用域是允许 Cookie 在子域名当中生效的,而通过浏览器默认设置是不允许子域名的。

设置 Domain 时也需要遵守一些规则,假设当前为 foo.bar.com,只能设置当前域名以及上级域名,但不能直接设置为顶级域名(如 .net.com)、子域名(如 child.foo.bar.com)、或者其他公共域名(如 github.io),正确的设置方法为 foo.bar.com 或者 bar.com。如果没有正确设置 Domain 浏览器会拒绝该 Cookie。

Path 指定了 Domain 匹配到的域名下的哪些路径可以接受该 Cookie

SameSite 要求 Cookie 在跨站请求的时候不会被发送,从而阻止跨站请求伪造攻击 CSRF

恶意网站会在诱导用户发送带有攻击目标网站请求的链接或者表单,如果用户浏览器中有目标网站的 Cookie,目标网站就会收到带有正确 Cookie 的请求。这种第三方网站引导而附带发送的 Cookie,就称为第三方 Cookie。
除此之外就是网站通过 Cookie 跟踪用户,通过在第三方网站嵌入一些自己网站的请求,浏览器在加载页面时候就会发出带有用户 Cookie 的请求,这样就能够得知用户访问了哪些网站。

SameSite 有三个值:

  • None 不做任何限制,第三方网站也可以向服务器发出带有 Cookie 的请求
  • Strict 严格限制,任何第三方网站的请求都不可以携带该 Cookie,只在 Domain 规定的域名下的请求才可以携带该 Cookie
  • LaxStrict 相似,只有在用户是通过第三方网站的链接跳转过来的时候才会携带该 Cookie,为一些跨站子请求保留。具体规则可以参考下表:

以前,如果 SameSite 属性没有设置,或者没有得到运行浏览器的支持,那么它的行为等同于 None,Cookies 会被包含在任何请求中——包括跨站请求。

大多数主流浏览器正在将 SameSite默认值迁移至 Lax。如果想要指定 Cookies 在同站、跨站请求都被发送,现在需要明确指定 SameSiteNone

Secure, HttpOnly

有两种方法可以确保 Cookie 被安全发送,并且不会被意外的参与者或脚本访问:Secure 属性和 HttpOnly 属性。
Scure 属性只允许 Cookie 通过 HTTPS 加密过后的请求,而不支持 HTTP 协议。但需要注意的是,即便如此也不应当通过 Cookie 发送任何敏感信息。

从 Chrome 52 和 Firefox 52 开始,不安全的站点(http:)无法使用Cookie的 Secure 标记。

HttpOnly 规定 Cookie 不能通过 Javascript 的 Document.cookie API 等其他方式直接获取。这样可以防止恶意脚本的攻击。

通过 JavaScript 访问和创建 Cookie

JavaScript 提供了 Document.cookie 来访问当前 Cookie,当然前提是该 Cookie 没有设置 HttpOnly 属性。

该方法会返回一个字符串包含所有的Cookie,每条cookie以”分号和空格(; )”分隔(即 key=value 键值对),可以尝试使用 String.Prototype.split 手动将它们分离开来。

const cookies = Document.cookie.split(';');// type of cookies is Array;

该方法也可以用来创建一个 Cookie。
和访问 Cookie 不同,需要注意的是,创建 Cookie 时一次只能创建一条,而且需要以 键值对 的形式创建,例如:

document.cookie = "foo=bar;domain=example.com;path=/home;";
  • path属性必须为绝对路径,默认为当前路径。
  • domain属性值必须是当前发送 Cookie 的域名的一部分。比如,当前域名是 example.com,就不能将其设为 foo.com。该属性默认为当前的一级域名(不含二级域名)。
  • max-age 属性的值为秒数。
  • expires 属性的值为 UTC 格式,可以使用 Date.prototype.toUTCString() 进行日期格式转换。
  • Cookie 的值字符串可以用 encodeURIComponent() 来保证它不包含任何逗号、分号或空格( Cookie 值中禁止使用这些值).

参考资料:

使用 RSS 在推荐算法中获取主动权

作者 Moeyua
2021年1月30日 21:25

前言

当学习或者工作感到疲惫时,我们通常会放下手里的事情,拿起手机休息一下,这倒也无可厚非,但是今天我们似乎很难自己掌控休息的时间,往往拿起手机就被各种内容吸引,各类软件依靠着自己的推荐算法,总是能带来新鲜的信息,但是这些信息并不能带给我太多东西,比起零碎的信息还是系统性的的知识比较有用。

为了让我们回归工作和学习,解决信息过载,获取接受信息的主动权,RSS 或许是一个很好的选择,我们可以通过 RSS 主动选择和调整订阅源,摆脱推荐算法,让获取信息变的简单。

鉴于熟练RSS需要一定的学习,这篇文章就想详细讲讲什么是 RSS ,如何使用 RSS 在网络中获取主动权,争取让小白也可以看懂。

什么是RSS

RSS 其实并不是什么新鲜的技术,相反 RSS 是在被成为 WEB1.0 时代就已经出现的技术,我们来看一下维基百科上面对 RSS 的定义:

RSS(全称:RDF Site Summary;Really Simple Syndication),中文译作「简易信息聚合」,也称「聚合内容」,是一种消息来源 格式规范,用以聚合经常发布更新资料的网站,例如 博客 文章、新闻、音频 或 视频 的网摘。RSS 文件(或称做摘要、网络摘要、或频更新,提供到频道)包含全文或是节录的文字,再加上发布者所订阅之网摘资料和授权的元数据。简单来说,RSS 能够让用户订阅个人网站个人博客,当订阅的网站有新文章是能够获得通知。

Really Simple Syndication「简易信息聚合」,听名字就能知道这是一个十分简单的技术,简单来说 RSS最主要的目的就是给个人网站和博客提供信息聚合,并通知所有订阅的阅读者,使信息能够更高效的传播。

RSS 能做什么

  • 可以看到没有广告和图片的标题或文章的摘要,这样你不必阅读全文即可知文章讲的一个意思是什么,方便确定是否要阅读本文。

  • RSS 阅读器会自动更新你定制的网站内容,保持新闻的及时性。这样每天你就可以在固定时间打开 RSS 阅读最新文章,而不必打开各个软件和网站,也不会被其他消息所干扰。

  • 使用 RSS 可以根据你自已的喜好定制多个 RSS 订阅源,这样做的好处是从多个网站来源搜集,然后整合到单个数据流当中。

  • 可以在 RSS 中的路由参数中选择订阅网站的某个栏目,比如知乎热榜,微博热搜,而不需要订阅整个网站。

  • 某些阅读器甚至提供了过滤功能,用户只需要提供关键词,阅读器就会自动过滤掉相应内容,极大的简化了我们的信息流

RSS怎么使用

RSS的使用方式实际上很简单,找到一个订阅源,然后添加到RSS阅读器当中刷新就能完成订阅,但实际情况却比这个麻烦很多————因为你并没有订阅源。

要想知道一个网站是否支持RSS,最直接的方法就是看网站的底部或侧边栏是否有 RSS 图标。一般来说,图标所指向的地址就是该网站的订阅链接,你可以直接点击 RSS 订阅链接跳转到 RSS 客户端内进行订阅,也可以复制粘贴按钮中的地址到自己在用的 RSS 服务中订阅这些网站中的内容。

以我的 博客 为例,上方导航栏的 RSS 即为订阅链接,复制或点击即可进行订阅,移动端可以长按图标复制订阅源。


是复制链接而不是链接里的内容!!

RSSHub

现在我们已经知道如何添加订阅源了,但是你会发现很多主流网站像微博,哔哩哔哩,知乎等并没有支持 RSS,那么这时候就需要介绍一个开源项目————RSSHub

RSSHub 是一个开源、简单易用、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源。RSSHub 借助于开源社区的力量快速发展中,目前已适配数百家网站的上千项内容

以上内容来自RSSHub的官网介绍,就像官网所说的一样,通过RSSHub确实能够做到 万物皆可 RSS ,你可以通过 https://docs.rsshub.app/ 来访问他们的官网,里面由很详细的文档以及许多RSS路由,这些RSS几乎涵盖了生活中涉及到的所有方面,所以我们就不需要自己去各个网站寻找RSS图标了


接下来我就以微博为例展示如何使用RSSHub

注意路由部分
这里的路由中没有带”:” 表示为固定内容,不需要更改,而带有冒号的表示为参数,需要使用者自行配置。这里有 uid 和 routeParams 两个参数,其中第一个参数为必选参数,第二个参数为可选择参数,后者可以根据个人需求进行配置,这里只展示第一个参数 uid:

  1. 首先我们打开博主主页,按下F12启动控制台
  2. 切换到控制台( console )选项卡
  3. 在一堆提示最下面执行 $CONFIG.oid
  4. 得到的数字就是我们需要的参数 uid
  5. 用我们得到的 id 替换路由中的 :uid

这样我们就能够得到博主“游点艺术”微博的RSS
https://rsshub.app/weibo/user/2040839563

接下来只需要复制这个订阅源,到阅读器中订阅,我们就可以在阅读器中接收到博主的微博了。

RSSHub 还支持自建路由和浏览器插件,感兴趣的可以参考官方文档。

RSS 阅读器

RSS 阅读器我个人使用过 Inoreader 以及 ios 端的 Reeder4,目前在使用的是 Reeder4。

Reeder4

官网:https://reederapp.com/

不支持中文界面

Reeder 是 iOS 和 Mac 的老牌阅读器了,说是最好的阅读器也不过分,它除了可以让你手动加入 RSS 频道外,也可以从 Feedbin,Feedly,Inoreader,The Old Reader 等 RSS 阅读平台导入数据,程序本身支持 iCloud Reader 稍后,Pocket,Instapaper 等稍后阅读服务。在 iOS 平台手势操作也是一大亮点,标题左划 Star 文章,右划标记为已读,文章页左划查看源网站,几乎所有操作都可以用手势完成。

Inoreader

官网:https://www.inoreader.com/

支持中文界面

就功能而言 Inoreader 更加丰富,直观的界面设计,高级模式甚至支持去重功能,避免当热点新闻发生时避免被大量内容重复的文章刷屏,也支持关键词过滤,增强订阅源等等。免费版的功能也足够非重度用户使用,且支持 Win,Android,iOS,Mac 以及浏览器使用。推荐新手入门使用

推荐订阅源

https://moeyua.notion.site/RSS-1eda6578a8cf474eb9d0634821119334

这里是我个人使用的部分订阅源,是当作笔记写在Notion上的,因为并没有什么内容,暂时不打算搬到博客上来,有需要可以自行查看。

参考资料

  1. 论 RSS 的「复兴」
  2. RSS-Wikipedia
  3. Web 源与内容聚合: RSS/Atom 的扩展、生成、发布、发现与共享
  4. RSSHub
  5. 高效获取信息,你需要这份 RSS 入门指南
❌
❌