跳到主要内容

如何利用 AI 做乘法,制作一款龙年贺卡小程序

· 阅读需 5 分钟

2022 年底 AIGC 的出现,让 2023 年成为通用人工智能元年

这是最好的时代,利用 AI,之前仅能存在幻想中的事物落地成现实。

只需要寥寥几句话,就可以描绘一张斑斓的画,真实而又丰富的画。

目前 AI 生图的大模型不多,大名鼎鼎的有 Midjourney,不过它闭源,并且国内用户使用不方便

Stable Diffusion,一款免费开源,而又强大的 AI 生图模型,正在成为 AI 绘画的宠儿。

比如这张庆祝新年的图片,正是出自 Stable Diffusion 之手:

Stable Diffusion 支持文生图(通过提示词文字生成图片)、图生图(通过垫图+提示词文字生成图片)、文生视频视频生成视频

如下是一个图生图的例子,通过一张手绘的垫图(左侧),生成一张真实的苹果图片(右侧):

还有比如前阵子抖音比较火的 AI 变换视频:

为了探索 Stable Diffusion 想象力的边际,在临近甲辰龙年之际,我们决定开发一款龙年贺卡小程序,效果图如下:

我们选择了一个叫做 Comfy UI 的开源框架,基于工作流的交互,让 Stable Diffusion 的编排和出图更加方便和快捷:

Comfy UI 官方介绍称之为:“最强大的 Stable Diffusion 的图形化操作界面

👉 官网链接: https://github.com/comfyanonymous/ComfyUI

虽然有 Comfy UI 加持,但在一开始,就碰壁了,无法生成文字,如下:

左边这张「龙年大吉」是我们输入给 Stable Diffusion 垫图,右边是图片是基于垫图和提示词生成的图片。

可以看到,文字根本是混乱的。

这个痛点强如 Midjourney 也无法解决,这是由于文字的特殊性

就说汉字,书体有篆书、隶书、楷书、行书、草书等,排列有从左往右、从右往左、从上往下等。

另外一种解决思路是,我们把文字也作为垫图之一,比如这张「龙年大吉」的图片:

生成结果:

这个方案似乎能走得通,但是很快又遇到麻烦,对于稍微复杂的字,生成效果就不行了,比如「甲辰臻祥」:

为了解决这个问题,加上时间的有限性,只能在产品层面做出改动,贺卡图片由模板 + 主体图片组成,模板提前预制,主体图片则由 AI 生成,不包含文字:

另外一个问题,就是涉及到人物,像手部、面部比较复杂的内容时,生成效果较差,如下小女孩的手部:

这个解决方案较多:

  1. 可以选择合适的 Stable Diffusion 模型
  2. 利用插件做手部、面部的修正
  3. 添加负面提示词(Negative Prompt)

有了如上的探索,我们终于开始我们的乘法之路。

首先是选定关于新年的元素(Element),比如龙、财神爷、灯笼、白鹤等元素。

其次是风格(Style),我们探索了十多种 Stable Diffusion 生成的风格:

Stable Diffusion 稳如老狗,各种风格都驾驭得住。

通过 Element×StyleElement \times Style,我们为每一个元素生成对应的风格图片。

乘法的力量是不言而喻的,在几周时间,我们为了贺卡小程序生成了上万张图片。

AIGC 之前,这绝对实现不了,能做的,只是加法,并且还需要专业的设计师。

假设一个设计师 5 分钟出一张图,一万张图片,不眠不休,至少要 34 天:

10000×5÷60÷24=34.722222222210000 \times 5 \div 60 \div 24 = 34.7222222222

这种人力成本、时间成本,一下子就被 AI 打下来了

目前 AI 还处于早期阶段,相信随着时间推移,AI 能力会更强,应用的场景会更广,拭目以待。

目前小程序也以及正式上线,名字很有年味,叫做「画年

操作简单,选择一个钟意的模板,一键生成:

如果不满意,可以调整配图 or 祝福语:

如果你有拜年贺卡需求,不妨体验一下这款小程序,目前免费使用。

ChatGPT 创始人 Sam 有效提高效率的秘诀

· 阅读需 5 分钟

这是一篇 ChatGPT 创始人山姆·奥特曼(Sam Altman)在 2018 年写的一篇博客,关于如何提升效率的有效建议。

你在做什么?

长期主义的本质是复利公式

复利是一个金融术语,但它也适合在个人的职业上。如果你每天都在产出,那么 50 年后你的复利获益将会是巨大的。提高自己的效率是一件非常重要的事情,从原来每天的 1% 提升到 10%,复利获益会变得更大。

复利公式见 👉 长期主义的本质是复利公式

提高效率的核心:做你自己喜欢的事情

在错误的方向上前进就是在倒退,花点时间去深入思考你自己喜欢做的事情,只有自己热爱的事情,才能真正地提高你的生产效率。

把自己不喜欢的事情委托给喜欢的人

做自己喜欢的事情才能让自己效率倍增,但面对自己不喜欢的事情,那么尽量不要去做它,比如采取委托、避免,或者其他方式。委托给别人的时候要注意,要委托给那些喜欢做这件事情的人,因为从“做你喜欢的事情可以提高效率”这一原则来说,应该也要让被委托者高效地完成这件事情。

坚持自己的坚持

这些年能够留给我深刻印象的是那些有强烈信念的人,这种人在大众中很罕见。如果你发现你总是附和别人,那可不是一件好事。坚持自己的坚持,有时候可能会出错,但当其他人都在迷茫的时候,你却做了正确而重要的事情,那么你将会变得更加勇敢非凡。

避免和消耗你的人在一起

要完成一件伟大的事情,应该努力和那些聪明、积极、高效和开朗的人在一起,他们不会随意贬低你的理想和抱负,并且他们可以推动你前进,也能给你带来某些启发。在你能力范围之内,避免和让你陷入精神内耗的人一起,否则代价将会是巨大的。

找对问题并解决它

我们基本上都身处红海行业,竞争激烈,我们要做的就是聪明而勤奋地工作,找到正确的问题,并正确地去解决它,这方面没有太多的捷径。

优先级

三个基本原则

  1. 完成重要的事情
  2. 别把时间浪费在 SB 事情上
  3. 做大量的清单

强烈推荐使用清单

清单写下每年、每月和每天的任务,清单可以让你聚焦当下的任务,并且能够让你处理多个任务(因为你的大脑释放出来,不需要记住那么多的任务)。

可以把清单写在纸上,方便添加和移除,开会的时候进行记录也不会冒犯别人。

经常回顾清单,及时修改或者删除任务,不做分类和排序这样耗费时间的事情,重要的事情就打个“⭐️”。

优先做产生势头的事情

优先做那些可以产生“势头”的事情,你做完一件,产生成就感,继续去做下一件事情

学会冷酷无情

“为了要完成我最重要的项目,我会变得坚韧残酷——我发现只要我真心想要做成某件事,并且付出足够的努力,通常都能实现。”

I am relentless about getting my most important projects done—I’ve found that if I really want something to happen and I push hard enough, it usually happens.

“我尝试变得残酷,对于无关紧要的事情说不,对于非关键任务会以最快速的方式实现。我可能做得过头了—比如,我几乎可以确定在回复邮件时,我的简洁到了无礼的程度。

“I try to be ruthless about saying no to stuff, and doing non-critical things in the quickest way possible. I probably take this too far—for example, I am almost sure I am terse to the point of rudeness when replying to emails.”

维护一个开放的社交网络

努力避免会议和研讨会,因为这会浪费大量的时间和精力。但是日程上,也要保持一定的开放性,去接触一些新的人和想法,维护一个开放的社交网络是很重要的。在研讨会上,虽然 90% 都是浪费时间,但 10% 有价值的东西可以弥补这样的损失

物理方面

这一方面围绕睡眠、锻炼和营养展开,有些老生常谈,并且 Sam 是一个极度素食主义,这里忽略,感兴趣可以见原文。

其他方面

舒适的办公环境

办公室安静不会被人打扰,拥有自然光和一台漂亮的办公桌,以及很多台 4K 显示屏。

一些小妙招

  • 写一些自定义的小工具,让它们去干那些烦人、琐碎重复的工作。

  • 提高打字速度

  • 学习快捷键

糟糕的大姨妈

像很多人一样,偶尔会有一两周丧失斗志,对任何事情都提不起劲

暂时没有什么好的解决方法,但是相信“大姨妈”总是会过去,过去之后又是精神抖擞,继续奋斗。

另外,在心情很差的时候,避免和其他人接触,这是一个良心建议。

重视爱人和家庭

不要为了效率去忽略你的爱人和家庭,你提高了效率,却失去了快乐

最后

Sam 的提高效率的观点值得参考,同时他也强调了:不要掉入提高效率陷阱中,提高效率不是目的,我们的目的是为了快速、质量地完成任务

程序员提高效率的 10 个方法

· 阅读需 6 分钟

1. 早上不要开会 📅

每个人一天是 24 小时,时间是均等的,但是时间的价值却不是均等的,早上 1 小时的价值是晚上的 4 倍。为什么这么说?

因为早晨是大脑的黄金时间,经过一晚上的睡眠,大脑经过整理、记录、休息,此时的状态是最饱满的,适合专注度高的工作,比如编程、学习外语等,如果把时间浪费在开会、刷手机等低专注度的事情上,那么就会白白浪费早上的价值。

2. 不要使用番茄钟 🍅

有时候在专心编程的时候,会产生“心流”,心流是一种高度专注的状态,当我们专注的状态被打破的时候,需要 15 分钟的时候才能重新进入状态。

有很多人推荐番茄钟工作法,设定 25 分钟倒计时,强制休息 5 分钟,之后再进入下一个番茄钟。本人在使用实际使用这种方法的时候,经常遇到的问题就是刚刚进入“心流”的专注状态,但番茄钟却响了,打破了专注,再次进入这种专注状态需要花费 15 分钟的时间。

好的替换方法是使用秒表,它跟番茄钟一样,把时间可视化,但却是正向计时,不会打破我们的“心流”,当我们编程专注度下降的时候中去查看秒表,确定自己的休息时间。

3. 休息时间不要玩手机 📱

大脑处理视觉信息需要动用 90% 的机能,并且闪烁的屏幕也会让大脑兴奋,这就是为什么明明休息了,但是重新回到工作的时候却还是感觉很疲惫的原因。

那么对于休息时间内,我们应该阻断视觉信息的输入,推荐:

  • 闭目养神 😪
  • 听音乐 🎶
  • 在办公室走动走动 🏃‍♂️
  • 和同事聊会天 💑
  • 扭扭脖子活动活动 💁‍♂️
  • 冥想 or 正念 🧘

4. 不要在工位上吃午饭 🥣

大脑经过一早上的编程劳累运转之后,此时的专注度已经下降 40%~50%,这个时候我们需要去重启我们的专注度,一个好的方法是外出就餐,外出就餐的好处有:

  • 促进血清素分泌:我们体内有一种叫做血清素的神经递质,它控制着我们的睡眠和清醒,外出就餐可以恢复我们的血清素,让我们整个人神经气爽:
    • 日光浴:外出的时候晒太阳可以促进血清素的分泌
    • 有节奏的运动:走路是一种有节奏的运动,同样可以促进血清素分泌
  • 激发场所神经元活性:场所神经元是掌控场所、空间的神经细胞,它存在于海马体中,外出就餐时场所的变化可以激发场所神经元的活性,进而促进海马体活跃,提高我们的记忆力
  • 激活乙酰胆碱:如果外出就餐去到新的餐馆、街道,尝试新的事物的话,可以激活我们体内的乙酰胆碱,它对于我们的“创作”和“灵感”起到非常大的作用。

5. 睡午觉 😴

现在科学已经研究表现,睡午觉是非常重要的一件事情,它可以:

  • 恢复我们的身体状态:26 分钟的午睡,可以让下午的工作效率提升 34%,专注力提升 54%。
  • 延长寿命:中午不睡午觉的人比中午睡午觉的人更容易扑街
  • 预防疾病:降低老年痴呆、癌症、心血管疾病、肥胖症、糖尿病、抑郁症等

睡午觉好处多多,但也要适当,15 分钟到 30 分钟的睡眠最佳,超过的话反而有害。

6. 下午上班前运动一下 🚴

下午 2 点到 4 点是人清醒度最低的时候,10 分钟的运动可以让我们的身体重新清醒,提高专注度,程序员的工作岗位和场所如果有限,推荐:

  • 1️⃣ 深蹲
  • 2️⃣ 俯卧撑
  • 3️⃣ 胯下击掌
  • 4️⃣ 爬楼梯(不要下楼梯,下楼梯比较伤膝盖,可以向上爬到顶楼,再坐电梯下来)

7. 2 分钟解决和 30 秒决断 🖖

⚒️ 2 分钟解决是指遇到在 2 分钟内可以完成的事情,我们趁热打铁把它完成。这是一个解决拖延的小技巧,作为一个程序员,经常会遇到各种各样的突发问题,对于一些问题,我们没办法很好的决策要不要立即完成, 2 分钟解决就是一个很好的辅助决策的办法。

💣 30 秒决断是指对于日常的事情,我们只需要用 30 秒去做决策就好了,这源于一个“快棋理论”,研究人员让一个著名棋手去观察一盘棋局,然后分别给他 30 秒和 1 小时去决定下一步,最后发现 30 秒和 1 小时做出的决定中,有 90% 都是一致的。

8. 不要加班,充足睡眠 💤

作为程序员,我们可能经常加班到 9 点,到了宿舍就 10 点半,洗漱上床就 12 点了,再玩会儿手机就可以到凌晨 2、3 点。

压缩睡眠时间,大脑就得不到有效的休息,第二天的专注度就会降低,工作效率也会降低,这就是一个恶性循环。

想想我们在白天工作的时候,其实有很多时间都是被无效浪费的,如果我们给自己强制设定下班时间,创新、改变工作方式,高效率、高质量、高密度的完成工作,那是否就可以减少加班,让我们有更多的自由时间去学习新的知识技术,进而又提高我们的工作效率,形成一个正向循环。

9. 睡前 2 小时 🛌

  1. 睡前两小时不能做的事情:
    • 🍲 吃东西:空腹的时候会促进生长激素,生长激素可以提高血糖,消除疲劳,但如果吃东西把血糖提高了,这时候生长激素就停止分泌了
    • 🥃 喝酒
    • ⛹️ 剧烈运动
    • 💦 洗澡水过高
    • 🎮 视觉娱乐(打游戏,看电影等)
    • 📺 闪亮的东西(看手机,看电脑,看电视)
    • 💡 在灯光过于明亮的地方
  2. 适合做的事情
    • 📖 读书
    • 🎶 听音乐
    • 🎨 非视觉娱乐
    • 🧘‍♂️ 使身体放松的轻微运动

10. 周末不用刻意补觉 🚫

很多人以周为单位进行休息,周一到周五压缩睡眠,周末再补觉,周六日一觉睡到下午 12 点,但这与工作日的睡眠节奏相冲突,造成的后果就是星期一的早上起床感的特别的厌倦、焦躁。

其实周末并不需要补觉,人体有一个以天为单位的生物钟,打破当前的生物钟周期,就会影响到下一个生物钟周期,要调节回来也需要花费一定时间。

我们应该要以天为单位进行休息,早睡早起,保持每天的专注度。

参考

以上大部分来源于书籍 《为什么精英都是时间控》,作者桦泽紫苑,是一个脑神经专家。

10 分钟把你的 Web 应用转为桌面端应用

· 阅读需 5 分钟

在桌面端应用上,Electron 也早已做大做强,GitHub桌面端、VSCode、Figma、Notion、飞书、剪映、得物都基于此。但最近后起之秀的 Tauri 也引人注目,它解决了 Electron 一个大的痛点——打包产物特别大

我们知道 Electron 基于谷歌内核 Chromium 构建,打包后无论应用多小,至少都得 70M 起步,而 Tauri 使用操作系统内的 Webview1,运行时才会去动态连接 webview,这使得它的打包速度非常快、打包后的应用更小

Tauri 跟 Electron 一点不同,Electron 使用 JavaScript 编写后台服务,而 Tauri 则使用 Rust,Rust 这两年势头很猛,更安全、性能更好,很多应用都开始转入 Rust 的怀抱,相信不久后也会是前端必学基础。

本文就基于 Tauri 作为构建桌面端应用框架,仅需一点时间,就可以将一个 Web 应用转为桌面端应用

1. 打开一个 Web 应用

我们以 FocusTide 这个应用为转换对象,它是 GitHub 开源的一个计时 Web 应用:

首先我们先 Clone 该仓库到本地:

$ git clone git@github.com:Hanziness/FocusTide.git

然后我们安装并且运行起来:

# 安装依赖
$ yarn install

# 启动服务,在 localhost:3000
$ yarn dev

2. 安装 Tauri 依赖

我们以 Mac 为例,Mac 下需要安装 CLang 和 MacOS 相关开发依赖:

$ xcode-select --install

安装 Tauri:

$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

这条命令会安装下载 Rustup,它会下载安装 Rust 相关依赖,当安装成功后控制台会显示:

$ Rust is installed now. Great!

其他操作系统安装 Tauri:

  1. Windows 下安装 Tauri:https://tauri.app/zh-cn/v1/guides/getting-started/prerequisites/#windows
  2. Linux 下安装 Tauri:https://tauri.app/zh-cn/v1/guides/getting-started/prerequisites/#linux

3. 将 Tauri 集合到项目中

在 Web 应用内,先安装 Tauri 相关的 npm 包

$ npm install --save-dev @tauri-apps/cli

pakcage.json 增加脚本命令:

"scripts": {
"tauri": "tauri"
}

接着跑 Tauri 初始化项目命令:

$ npm run tauri init

这条命令执行之后,会在当前 Web 项目产生如下 Tauri 项目:

执行之后,会有一些问题需要我们去填写

  1. What is your app name? 应用名。这个名字会作为打包后应用的正式名称。

  2. What should the window title be? 默认窗口名称。这个会作为我们打开应用窗口的名称,后续我们不需要这个窗口的话我们可以在 tauri.conf.jsonhiddenTitle 字段去隐藏它。

  3. Where are your web assets (HTML/CSS/JS) located relative to the < current dir $gt; /src-tauri/tauri.conf.json file that will be created? 生产环境下的文件路径。也就是前端项目打包之后的项目路径,这个路径相对于 /src-tauri/tauri.conf.json 路径。FocusTide 项目打包产物放在项目的 dist 文件夹中,所以我们填 ..dist

  4. What is the URL of your dev server? 开发环境下的服务路径。FocusTide 项目开发下的服务路径为 http://localhost:3000

  5. What is your frontend dev command? 前端启动开发命令。FocusTide 项目启动开发命令是 yarn dev

  6. What is your frontend build command? 前端打包命令。FocusTide 项目的打包命令是 yarn generate

执行后,生成 src-tauri,接着我们就可以把项目跑起来了:

$ npm run tauri dev

可以看到,我们的应用在窗口跑起来了:

4. 打包发布

如果开发 OK,我们就可以把应用打包出来。

$ npm run tauri build

执行打包命令后,打包应用会存放在 src-tauri/target/release/bundle 下,可以看到,打包产物非常小:

Tauri 打包过程中,会更根据当前系统平台打包,比如 Mac 下只能打包 .dmg.app 包,Windows 下打包 .msi.exe 包。

5. 部署

下载后,如果我们要公开这个应用,需要部署应用,这里我们推荐 Laf ,我们可以使用它的云存储#文件管理,把我们的应用上传上去并且得到下载链接:

6. 最后

整个转换过程其实非常简单快速,如果你花费了超过 10 分钟的话,那我们深表歉意。如果你要继续深入使用桌面后台服务,可以查看 Tauri 官网

最后,我把转换后的 FocusTide 项目放到了个人 GitHub 上,并且取名为「来做」,目前仅限 Mac 端,欢迎下载 👏🏻

Footnotes

  1. Tauri vs. Electron: A comparison, how-to, and migration guide: https://blog.logrocket.com/tauri-electron-comparison-migration-guide/

bit, byte, KB, GB, MG, TB, PB

· 阅读需 1 分钟

1bit 是计算机中最小的数据单位,1bit 就对应一个高低电位。

在计算机上下文中,各单位的换算关系如下:

1bit×8=1byte1bit \times 8 = 1byte 1byte×1024=1KB(kilobyte)1byte \times 1024 = 1KB (kilobyte) 1KB×1024=1MB(megabyte)1KB \times 1024 = 1MB (megabyte) 1MB×1024=1GB(gigabyte)1MB \times 1024 = 1GB (gigabyte) 1GB×1024=1TB(terabyte)1GB \times 1024 = 1TB (terabyte) 1TB×1024=1PB(petabyte)1TB \times 1024 = 1PB (petabyte)
注意

在十进制或者国际单位制中,会用 1000 作为进率,而不是 1024。注意区分上下文。


在 UTF-8 编码中

  1. 一个 US-ASCII 字符只需要 1byte
  2. 带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字符需要 2byte
  3. 其他语言(包括中日韩文字、东南亚文字、中东文字等)使用 3byte
  4. 极少数语言用 4byte

简单好用的 SemVer:如何命名你的应用版本

· 阅读需 1 分钟

SemVer 是目前较为流行的命名版本的规范,它的全称是 Semantic Versioning Specification,语义化版本命名规范。

它对版本的命名构成主要为:

  • X,Major 版本,版本迭代,会有兼容问题
  • Y,Minor 版本,更新功能,不应该有兼容问题
  • Z,Patch 版本,修复 Bug,更小程度的变动,不会有兼容问题
  • Pre-release,预发布版本,主要是 alpha、beta、insiders,正式发布前的不稳定版本,用于内部、社区、提前体验的用户
  • Build,一般是打包构建时的 commit id

SemVer 的规则简单明了,学习成本极低,这大概就是为什么 SemVer 如此流行的原因了。

[1] https://semver.org/spec/v2.0.0.html

[2] https://www.baeldung.com/cs/semantic-versioning

包含块如何决定元素的尺寸和位置

· 阅读需 11 分钟

一、包含块

在学习 CSS In Depth 的 3.2.2 节时,被一个关于循环定义(circular definition)困住了:

Percentage refers to the size of an element's containing block; the height of that container, however, is typically determined by the height of tis children. This produces a circular definition that the browser can't resolve, so it'll ignore the declaration. For percentage-based heights to work, the parent must have an explicitly defined height.

百分比指的是元素的“包含块”的大小;然而,该容器的高度通常由其子元素的高度决定。这会产生一个浏览器无法解决的循环定义,因此浏览器会忽略该声明。要使基于百分比的高度起作用,父元素必须有明确定义的高度。

这里提到了一个包含块(Containing Block),What is Containing Block?

别慌,相信学完之后,你同样能够对这个问题的答案醍醐灌顶。

首先,看一下 MDN 关于包含块的文章:

image.png

一个元素的尺寸和位置经常被包含块所影响。大部分情况下,包含块指向一个元素最近的块级祖先元素的内容区域,小部分情况下不是这样的。在这篇文章中,我们会研究那些确定一个元素包含块的因素。

虽然这里翻译为祖先元素(ancestor),但其实更贴切是长辈元素,因为这里不仅指祖先元素,也指父类元素。想象一下,一个 <div> 被嵌套了几层,它的包含块就是第一个遇到的外层块级元素(注意是块级元素,也就是会忽略掉内联元素),第一个遇到的无论是父类还是祖父类们,都可以。

image.png

当一个用户代理(例如你的浏览器)展开一份文档,它会为每个元素生成一个盒子,每个盒子可以分成四个区域

  1. 内容区域
  2. 内边距区域
  3. 边框区域
  4. 外边距区域

box-model.png

其实也就是盒模型。

image.png

许多开发者坚信一个元素的包含块一定是它父类的内容区域,但这一结论不一定总是成立。让我们来研究一下确定一个元素的包含块的因素。

这里重复强调了下,元素的包含块是根据不同条件确定的。

二、包含块的效果

image-20220113223746573.png

在我们学习确定一个元素的包含块之前,了解一下为什么包含块的作用性是很重要的。

一个元素的尺寸和位置经常被它的包含块所影响。应用在 width, height, padding, margin 上的百分比值,还有 absolute 定位的元素偏移属性,都是根据元素的包含块计算出来的。

像百分比这样的值,肯定根据一个基准来进行计算,得到最后的结果值,而包含块就是那个基准。

三、标识一个包含块

image.png

标志包含块的程序完成取决于元素的 position 属性

  1. 如果 position 属性是 static, relative 或者 sticky,包含块通过最近长辈元素的内容盒的边界来形成,这个长辈*素可以是一个块级容器(例如 inline-block, block 或者 list-item 元素),也可以是建立了格式化的上下文(例如一个 table 容器,flex 容器,grid 容器,或者块级容器本身)

一个元素如果不显示定义 position 的值的话,那么元素 position 的默认值为 static。大部分元素都是不定义 position 值的,也就导致了前文提到的:许多开发者坚信一个元素的包含块一定是它父类的内容区域。

image.png

如果 position 的属性值是 absolute,包含块变成了最近的长辈元素的内边距区域,这个最近的长辈元素指它的 position 属性值不是 static(可以是 fixed, absolute, relative 或者 sticky)的元素。

注意包含块的变化,position 的值从 static 的最近长辈元素的内容区域(见下图绿色线条区域)改为 absolute 确定的最近长辈元素的内边距区域(下图红色线圈出来的区域,是包括内边距的边缘的)。

image.png

❓ 为什么内容区域变为了内边距区域?

这样做的目的是为了忽略内边距,体现了绝对定位(absolutely position)的效果

image.png

如果position属性是fixed,包含块由窗口(在连续媒体情况下)或者页面区域(在页面媒体情况下)所确定。

根据MDN的另一篇文章,连续媒体一般指音频或者运动视频。

image.png

  1. 如果 position 的属性是 absolute 或者 fixed,包含块同样会变成最近长辈元素的内边距区域,这个最近的长辈元素符合下面条件:
    1. transform 或者 perspective 的值不是 none
    2. will-change 属性值是 transform 或者 perspective
    3. filter 的值不是 none 或者 will-change 的值不是 filter(仅在 firefox 有效);
    4. contain 的值是 paint(例如:contain:paint;)。

will-change 这个比较少见,它是 CSS 的一个属性,作用是向浏览器表明元素即将做出变化,浏览器因此可以对此进行针对性的优化,从而提高页面响应性能。

image.png

包含根元素(<html>)的矩形包含块称之为初始包含块。它有视口(对于连续媒体)或者页面区域(对于页面媒体)的尺寸。

根元素<html>也有他自己的包含块,那么对于html页面上每一个元素来说,都会拥有自己的包含块。

四、从包含块中计算百分比值

image.png

如上所述,但某个属性给定一个百分比值,它计算出来的结果取决与元素的包含块。这些属性包括盒模型属性还有偏移属性:

  1. height,top 还有 bottom 属性从包含块的 height 中计算它的百分比值
  2. width,left,right,padding 还有 margin 属性从它的包含块的 width 中计算它的百分比值。

五、一些例子

以下是一段简单的 html 代码:

<body>
<section>
<p>This is a paragraph!</p>
</section>
</body>

以下例子共用上面这一份 HTML 代码,只是 CSS 代码有所变化,当这些 CSS 代码变化的时候,推断一下元素的包含块,以及元素的百分比计算结果。

例1

body {
background: beige;
}

section {
display: block;
width: 400px;
height: 160px;
background: lightgray;
}

p {
width: 50%;
height: 25%;
margin: 5%;
padding: 5%;
background: cyan;
}

image.png

在这个例子中,<p>position 属性是 static,它的包含块是 <section>,因为它是块级容器,并且也是距离 <p> 最近的长辈元素。

那么,<p> 的各个百分比值计算等于

p {
width: 50%; /* 值等于 400px * 50% = 200px */
height: 25%; /* 值等于 160px * 25% = 40px */
margin: 5%; /* 值等于 400px * 5% = 20px */
padding: 5%; /* 值等于 400px * 5% = 20px */
background: cyan;
}

width,left,right,padding 还有 margin 属性从它的包含块的 width 中计算它的百分比值。

例2

body {
background-color: beige;
}

section {
display: inline;
background: lightgray;
}

p {
width: 50%;
height: 200px;
background-color: cyan;
}

image.png

在这个例子中,<p> 的包含块是 <body> 元素,因为 <section> 不是一个块级容器(因为display:inline)并且也没有建立格式化上下文。

<p> 的宽度百分比计算如下:

p {
width: 50%; /* 值等于 <body> 宽度的一半 */
height: 200px;
background-color: cyan;
}

例3

body {
background-color: beige;
}

section {
position: absolute;
left: 30px;
top: 30px;
width: 400px;
height: 160px;
padding: 30px 20px;
background: lightgray;
}

p {
position: absolute;
width: 50%;
height: 25%;
margin: 5%;
padding: 5%;
background-color: cyan;
}

image.png

在这个例子中,<p> 的包含块是 <section>,因为后者的 position 值是absolute<p> 的百分比值会被包含块的 padding 所影响,如果包含块的 box-sizing 属性值是 border-box 的话,那么情况就不一样了。

<p> 的宽度百分比计算如下:

p {
position: absolute;
width: 50%; /* (400 + 20 * 2)px * 50% = 220px */
height: 25%; /* (160 + 30 * 2)px * 25% = 55px */
margin: 5%; /* (400 + 20 * 2)px * 5% = 22px */
padding: 5%; /* (400 + 20 * 2)px * 5% = 22px */
background-color: cyan;
}

box-sizing: border-box; 属性的作用是 widthheight 包含 content + padding + margin,也就是 IE 盒模型(标准的 W3C 盒模型 widthheight 只指定content,不包含 padding 和 margin)。

<section> 添加了 box-sizing: border-box;,那么 <p> 的宽度百分比计算会变为:

p {
position: absolute;
width: 50%; /* 400px * 50% = 200px */
height: 25%; /* 160px * 25% = 40px */
margin: 5%; /* 400px * 5% = 20px */
padding: 5%; /* 400px * 5% = 20px */
background-color: cyan;
}

这里读者可以自行验证下。

例4

body {
background-color: beige;
}

section {
width: 400px;
height: 480px;
margin: 30px;
padding: 15px;
background: lightgray;
}

p {
position: fixed;
width: 50%;
height: 50%;
margin: 5%;
padding: 5%;
background-color: cyan;
}

image.png

在这个例子中,<p>positionfixed,所以它的包含块是初始包含块(在屏幕上,那个视窗)。因此,<p> 的尺寸根据浏览器窗口的尺寸改变而改变。

<p> 的各个属性百分比计算如下:

p {
/* 假设视窗的宽高比为:562px*612px */
position: fixed;
width: 50%; /* 562px * 50% = 281px */
height: 50%; /* 612px * 50% = 306px */
margin: 5%; /* 562px * 5% = 28.1px */
padding: 5%; /* 562px * 5% = 28.1px */
background-color: cyan;
}

这里的视窗尺寸可以用浏览器的小手机图标来模拟:

image.png

例5

body {
background-color: beige;
}

section {
transform: rotate(0deg);
width: 400px;
height: 160px;
background: lightgray;
}

p {
position: absolute;
left: 80px;
top: 30px;
width: 50%;
height: 25%;
margin: 5%;
padding: 5%;
background-color: cyan;
}

image.png

在这个例子中,<p>positionabsolute,所以它的包含块是<section><section>transform属性不为none,所以它能够称为<p>的最近长辈元素。

<p> 的各个属性百分比计算如下:

p {
position: absolute;
left: 80px;
top: 30px;
width: 50%; /* 400px*50%=200px */
height: 25%; /* 160px*25%=40px */
margin: 5%; /* 400px*5%=20px */
padding: 5%; /* 400px*5%=20px */
background-color: cyan;
}

六、总结

看到这里的话,恭喜你完成了对包含块的所有学习 🎉🎉🎉

我们来总结下,包含块就是用于确定元素的尺寸和位置的,每一个元素都拥有自己的包含块(<html>的包含块称之为初始包含块)。

如果 positionstatic,relative,sticky,那么元素的包含块是最近的长辈元素(displayblock,table,flex,grid元素的内容区域)。

如果是 absolute,那么是内边距区域。

如果是 fixed,那么有视窗来决定。

并且对于 absolutfixed,还有其他一些规则可以确定他们的长辈元素(比如 tranform 属性不为 none 时)。这一块无需记忆,需要的时候查阅一下即可。

不用中间变量实现数值交换

· 阅读需 1 分钟

介绍三种不用中间变量实现数值交换的方法,

1. 加减法

假设有变量

let a = 2, b = 5

交换

a = a + b // a = 7

b = a - b // b = 2

a = a - b // a = 5

缺点:

  • 只能处理数值
  • 数值太大相加的时候可能会溢出

2. 乘除法

有加减法那么就自然可以联想得到乘除法:

a = a * b // a = 10

b = a / b // b = 2

a = a / b // a = 5

缺点:

  • 会有精度损失
  • 被除数不能为 0

3. 异或法

异或是一种数学运算,不同为 1,相同为 0:

结果
000
011
101
110

在计算机中进行异或运算,需要先把数值换算成二进制,a 的二进制为 010b 的二进制为 101

a = a ^ b // 010 ^ 101 = 111

b = a ^ b // 111 ^ 101 = 010

a = a ^ b // 111 ^ 010 = 101

缺点:

  • 无法处理浮点型变量

总结

三种方法都是奇技淫巧,仅供学习使用,生产环境最好不要用。

并发和并行的简单区分

· 阅读需 2 分钟

刚开始学并发和并行时总是整得有点懵,现在终于下手来好好梳理着两者的关系了。

首先并发的单词是 Concurrency,词根 con - 的意思是“一起”,而 current - 词根的意思是“当前”,所以总的意思就是“一起发生的事情”

并行的单词是 Parallelism,词根 parallel - 是平行、并列的意思,所以总的意思就是“平行发生的事情”,也可以说“同时发生的事情”

举一个知乎答主提到的一个形象的例子:

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。

你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是『** 同时 **』。

从这位知乎答主的我们其实就能够知道:并行是并发的一种特殊情况。 并发只要求有处理多个任务的能力,而并行要求有 同时 处理多个任务的能力

借此,我们来梳理一下多核 CPU 和单核 CPU 的并发、并行性

首先是针对单核 CPU,单核 CPU 很明确,一个时刻只能做一件事情,所以它不可能具备并行性,但它可能具备并发性(如果这台 CPU 支持处理多个任务的能力的话)

其次是多核 CPU,多核 CPU 就是多个单核 CPU 组成的,那么同一时刻处理多个任务就是一件轻而易举的事情了,也就是具有了并行性(有并行性就一定有并发性,前面我们已经说了并行性是并发性的一种特殊情况)

简要介绍进制和 ASCII 码表

· 阅读需 4 分钟

1. 什么是进制

  • 进制 进制全称“进位计数制”,一种计数方式,比较常用的是十进制
    • 十进制 十进制就是逢十进一,所以十进制有 10 个数字表示——0,1,2,3,4,5,6,7,8

生活中除了十进制还有很多常见的进制,可能你没有注意到,比如时钟,秒数是 60 进制,秒针从 0 走到 59 后,再走一分钟逢 60 进一成为一分钟。

再说计算机,计算机中也可以采用十进制来代表数字,那为什么偏偏要用二进制呢?原因很简单,因为二进制只有 0 和 1,表示起来很简单。

那为什么说二进制为什么简单呢?因为计算机底层也是硬件,每次硬件在两条路径和十条路径中选择肯定前者来得轻松,只不过为了达到同样目的地,二进制需要多走几步。

2.话说二进制

我们先来看看十进制是怎么表示的,假设我们现在有一个十进制的数字 (3107)10(3107)_{10}

注意

  1. 这里数字 (3107)10(3107)_{10} 用括号括起来,下标是10,表示它是10进制 这个下标就是用来表明它是什么进制的,比如 (3107)8(3107)_{8}注意此时它的下标是 8,那它就不是十进制了,而是八进制了 另外,为什么平时不这些写呢?因为十进制太通用了,不写默认它就是十进制
  2. 除了这种表示方法,常用的还有后缀字母表示法,你可能遇到过: 比如 3107H,在数字后面加上字母H,表明它是个十六进制的数字

这个 (3107)10(3107)_{10} 我们拆开来看的话:

3×103+1×102+0×101+7×1003×10^3+1×10^2+0×10^1+7×10^0

我们再来看一个二进制数字(1101)2(1101)_2,它也可以表示为

1×23+1×22+0×21+1×201×2^3+1×2^2+0×2^1+1×2^0

看到这里你应该能明白到为什么叫做十进制和二进制的原因了吗?

再说进制的转化,二进制转十进制很简单,你把

1×23+1×22+0×21+1×201×2^3+1×2^2+0×2^1+1×2^0

接下来按十进制来计算

8+4+0+18+4+0+1

就等于 (13)10(13)_{10},直接写成 (1101)2(1101)_2=(13)10(13)_{10}

3.什么是ASCII码

  • ASCII ((American Standard Code for Information Interchange)美国信息交换标准代码),名字怪长的,我们只着眼于代码这两个字就好了

我们知道,计算机表示数字是比较简单的,但如何表示文本呢?比如 a、b、c、d 这些英文,甚至是标点符号、汉字等等

前人做了一个很好的替换,就是先建一个表,表中存放着这些文本符号

文本符号
a
b
c

再给这些文本编个顺序

号码文本符号
0a
1b
2c

你要取的时候,按照号码来就好了,想要 a 输入 1,想要 b 输入 2,依次类推(这时你可能会明白一点“代码”的意思)

那么 ASCII 码表是作为较早期的符号表,多早呢?1967年,当时美国人发明的,要知道美国人用的语言就 26 个字母,再加上一些乱七八糟的符号等等,用 128 个字符就能够涵盖了

那为什么是 128 这个数字呢?在前面提到,计算机是用二进制表示的,从 0 号开始编码,一直到 127 号,刚好就是 128 个号码

那么最小号码是 0 号,最大号码是 127 号,那么如果我们能用数字表示最大的号码,那么比他小的号码也都能表示,而 (1111111)2(1111111)_2 就等于 127,我们展开来看

1×26+1×25+1×24+1×23+1×22+1×21+1×201×2^6+1×2^5+1×2^4+1×2^3+1×2^2+1×2^1+1×2^0

另外,这个(1111111)2(1111111)_2 一共有7位,所以也可以说成用 7 位表示的ASCII码表