MoreRSS

site iconlhasa | 游钓四方修改

千禧年生,长途骑行、野钓路亚、振出并继、古典乐、摇滚、布鲁斯、茶叶爱好者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

lhasa | 游钓四方的 RSS 预览

三脚架到货了

2026-01-27 00:17:00

早上九点,顺风打电话来:"快递到了,放前台了啊。" 估摸着是那三脚架到了

这东西没有真不行,晚上出门一抹黑,不用长曝光,啥都拍不了

我有三个需求:轻量化、收纳短、伸缩1.5m,再三选择后,下单了富宝图旗下新品,空气三号 LITE

图一拆包后,我直接蒙了

周日在群里扯淡,看到 菜鸟之志 推荐的八爪鱼支架,我笑了,这什么玩意,长得那么招笑

真的受够了这种秒懂的日子

还别说,这工具不错,小巧,轻便,灵活度很高,推荐

八爪鱼是送的,但图三这个快拆套装我感觉买亏了。加了一百块钱,就换回这么一小块铁片,关键脚架本身就带一个

出门前纠结了半天要不要背双肩包,最后想想算了,索性直接挂在包拉链上了

有没有一种吕布骑狗的感觉,我这破尼康配上这个架子,多少有点高攀了

今天全程下雨,走在西湖边吹冷风,冻得头疼

回家发现买的电池也到了,价格方面,只有原装的二分之一,应该够我折腾了

天知道这支架是哪个小作坊代工的,漆水和碳布的质量,不敢苟同,我最垃圾的鱼竿,破烧火棍的碳纤维材料都比这个强,不如高性能的铝材料,甚至不如玻璃钢

他的旋转设计也有缺陷,不容易锁紧,又不敢用力拧,因为碳布根本受不了这种扭矩,哪怕你有交叉工艺,也顶不住,这不符合物理规律

再加上碳纤维这东西本身就是消耗品,树脂会氧化。三脚架这玩意,风吹雨打的,扭矩承受力只会越来越差。网上已经看到不少人把它拧断了,总之不推荐

MySQL 要没落了

2026-01-24 05:50:00

这两天突发奇想,在赶一个新项目

从早上 8 点写到晚上 11 点,除了去卫生间,人基本没动过,饭也没吃

这不是我多能坐得住,我只是

妄想一步登天,做不完就死磕。十几个小时是日常,通宵是常态,哪个月没有 4~5 次 20h + 作战记录?

GitHub Commits 一眼就能看出来:我从凌晨提交到凌晨,电子厂的狗都不会这么干

也许这就是倔驴 + 心流?

而我,天生心流圣体

老实说,我对“心流”没概念,只是前阵子偶然了解到。所以我并不明白,但我知道那是一种状态

当然,不吃饭是不行的

晚上 11:30,厨房炒个菜,微波炉热两个馒头,回屋再冲一大杯黑咖啡,两勺燕麦,少许牛奶,就算齐活

坐回电脑前,打开 BlogFinder,随手一翻,看到了阮一峰的周刊

心想,周五了

低头再看,周六了

然后,我看到了他的第一个推荐文章,心里“咯噔”一下:

点进去,翻译看看,整篇文章的核心观点就是:

如果你在意开源软件,就应该停止使用 MySQL

MySQL 虽然是基于 GPL v2 开源,实际上已经没有开源精神,因为 MySQL 正走向封闭

开发活跃度呈现断崖式下跌

这是 Otto 在 1 月 11 日给出的截图

数据显示,从去年 9 月开始,到今年 1 月 11 日,MySQL 几乎完全断更

我看到这里,第一反应是去查官方信息 MySQL 核心团队博客,无果后,再 Google 一下,事情就对上了

早在 9 月 11 日,外媒就曝出 Oracle 大幅裁减 MySQL 核心员工 (约 70 人) 的消息,甚至还有传闻称,MySQL 团队已被并入 HeatWave 部门,Oracle 要把资源优先投入 AI

新闻刚出来时,MySQL 还没断更

现在再把这张“断崖式提交图”放出来,那可谓是深水炸弹

更有意思的是,Otto 还特意把图做成黑白,再配上大字标语当封面

手段可见一斑,毕竟,这人是 MariaDB Foundation (玛利亚数据库基金会) CEO

而 MariaDB 是 MySQL 的最重要的分支与直接竞争者

当年 Oracle 宣布收购 Sun Microsystems 的当天,MySQL 原始作者之一 Monty Widenius 就发起了 MariaDB fork

有意思的是:

  • MariaDB:是以 Monty 的小女儿玛丽亚 (Maria) 的名字命名
  • MySQL :则以大女儿 (My) 命名

就连 MariaDB 的 LOGO 海狮,也是有故事背景的

The Story of our Sea Lion

It happened when Monty and his older daughter My were snorkeling on one of the islands in the Galapagos. Something big, brown and fast suddenly appeared at an arm’s distance, laughing in their faces. Fond memories of this fast and funny creature, scaring the tourists, popped into Monty’s mind when asked picking a logo for MariaDB. He wanted to adhere to the tradition of animals as symbols of Open Source projects.

闭门造车

下面是 Otto 的举证,他认为 MySQL 所有开发工作都在 Oracle 内部进行

而公开的 Bug 追踪器只是个摆设,Oracle 内部有自己的系统

更离谱的是,他声称:

那些提交 PR 的人,除了石沉大海,就只剩下第二个选择

被 Oracle 员工重新编写并抹去原作者的贡献记录,原作者仅在博客中被提及其名

Otto 还证言道:

我在 AWS 负责 RDS MySQL 和 MariaDB 团队时,部门员工极其抗拒向 MySQL 提交代码,因为Oracle 的接纳态度极其恶劣

技术倒退

MySQL 8.0.29 将 ALTER TABLE 的默认行为改为 in-place

结果导致大量生产环境数据库崩溃、数据损坏

而这个问题,直到 8.0.32 才算彻底修完

更大的问题在于:

自 MySQL 8.0 发布以来,整整六年,没有一次真正意义上的大版本更新,几乎没有像样的功能可以讲

甚至,有传言新版本的 MySQL 性能大幅下降,之后被 MySQL 性能专家 Mark Callaghan 进行基准测试后得出MySQL 9.5 的吞吐量比 8.0 版本竟然低了 15%

最后,Otto 顺势给出了“迁移方案”:

说自家的 MariaDB 作为 MySQL 的亲兄弟,有极高的兼容性,对于传统 LAMP 架构,可以无缝切换

他说得也没错,MariaDB 起源于 MySQL 5.1 分支,早期开发策略几乎完全对标 MySQL,以至于后来被开源社区认可,尤其在 Debian、Ubuntu、Fedora 等发行版中,直接成为默认数据库

说实话,我已经不想看下去了,我只是个普通人,一个喜欢编程的小学生

从初一第一次接触数据库开始,就是 MySQL

从上学、工作,到后来失业

我靠它吃了很多年饭

刚毕业那会儿,我为了面试,背得最多的就是 MySQL

对着镜子:一会儿 Indexing、一会儿 Concurrency 人都要疯了

所以,我对 MySQL,是有感情的

现在它要没落了,心里说不出的滋味

虽然国内教学体系还在用 MySQL,但现实已经很清楚了

MySQL 不可能再回到从前老大哥的位置了

哎,天道如来,也罢

杨公堤,湖上客船

2026-01-18 23:50:00

实现自动拼图

2026-01-18 23:48:00

这事半个月前就计划好了,本想放到 Photosuite 下个版本里

拖到现在,若不是为了昨天拍的一套图,我也懒得折腾

就是为了这口醋,才包的这顿饺子

我的目的很简单:不引入新的语法,而是通过换行来实现拼图

在 Markdown 中连续插入多张图片,只要它们之间没有非空白内容,就应被视为一个整体自动组合

![图1](1.jpg)
![图2](2.jpg)

![图3](3.jpg)

如何检测相邻图片

拼图分组思路由贪心算法实现:

当扫描到一张图片时,以它为起点向后遍历,将所有连续相邻的图片依次纳入同一组

一旦相邻关系被打断,就停止扩展,并根据组内图片数量决定是否生成拼图(不足两张则原样保留)

难点在于“相邻”的定义:

因为 Markdown 编译为 HTML 后,图片容器之间可能夹杂换行符或空白文本节点

所有,不能直接依赖 nextSibling 进行判断

// src/modules/imageGrid.ts

/**
 * 检查两个容器是否相邻
 * 
 * @param container1 - 第一个容器
 * @param container2 - 第二个容器
 * @returns 是否相邻
 */
function areContainersAdjacent(container1: HTMLElement, container2: HTMLElement): boolean {
  // 获取两个容器的父元素
  const parent1 = container1.parentElement;
  const parent2 = container2.parentElement;

  // 如果父元素不同,不是相邻的
  if (parent1 !== parent2 || !parent1) return false;

  // 获取父元素的所有子元素
  const siblings = Array.from(parent1.children);
  const index1 = siblings.indexOf(container1);
  const index2 = siblings.indexOf(container2);

  // 检查是否相邻(中间只能有文本节点或空白节点)
  if (index2 !== index1 + 1) {
    // 检查中间是否只有空白文本节点
    let hasNonWhitespace = false;
    let node = container1.nextSibling;
    while (node && node !== container2) {
      if (node.nodeType === Node.ELEMENT_NODE) {
        hasNonWhitespace = true;
        break;
      }
      if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {
        hasNonWhitespace = true;
        break;
      }
      node = node.nextSibling;
    }
    return !hasNonWhitespace && node === container2;
  }

  return true;
}

在解决了相邻判定的问题之后,分组逻辑本身就变得很简单了

当 Photosuite 扫描到一张图片时,会从当前位置向后查找连续相邻的图片,并将它们归为一组

出于美观考虑,我这里将单组图片数量限制为最多三张

// src/modules/imageGrid.ts

function processImageGrids(root: Element) {
  // 获取所有图片元素
  const images = Array.from(root.querySelectorAll("img"));

  // 用于标记已处理的图片
  const processed = new Set<Element>();

  for (let i = 0; i < images.length; i++) {
    const img = images[i];

    // 跳过已处理的图片
    if (processed.has(img)) continue;

    // 检查是否可以形成拼图
    const gridImages = [img];
    processed.add(img);

    // 查找连续的图片(最多3张)
    for (let j = i + 1; j < images.length && gridImages.length < 3; j++) {
      const nextImg = images[j];

      // 检查两个图片容器是否相邻
      const currentContainer = ensurePhotosuiteContainer(gridImages[gridImages.length - 1]);
      const nextContainer = ensurePhotosuiteContainer(nextImg);

      if (areContainersAdjacent(currentContainer, nextContainer)) {
        gridImages.push(nextImg);
        processed.add(nextImg);
      } else {
        break;
      }
    }

    // 如果找到了多张连续的图片(2-3张),创建拼图
    if (gridImages.length >= 2) {
      createImageGrid(gridImages);
    }
  }
}

我用 Boardmix 做了一个流程图,通俗易懂:

如何处理不同比例的图片

把图片简单地放进一个 Flex 容器并不难

如果一张是横图(16:9),另一张是竖图(9:16),直接使用 flex: 1 只会让它们宽度相同,却无法保证高度一致,结果要么高低不齐,要么图片被强行拉伸

要让多张图片在同一行里等高对齐,关键不在 Flex,而在比例关系

直观来说:哪张图片更“扁”,就应该占更宽的位置;哪张更“瘦”,就占得窄一些,这样它们的高度才能最终一致

用更具体的话说:

每张图片在一行中所占的宽度,应该和它本身的宽高比成正比

宽高比越大(越横),分到的宽度就越多;宽高比越小(越竖),分到的宽度就越少

因此,在等高布局下,可以把每张图片的宽度理解为:

每张图片的宽度占比 ≈ 自身宽高比 ÷ 所有图片宽高比之和

其中:宽高比 = 宽 / 高

基于这个思路,我没有让 Flex 自由分配空间,而是先计算好每张图片应占的宽度,再通过 flex-basis 精确控制布局

具体实现上,会先异步获取每张图片的宽高比,计算出总比例后,将容器宽度按比例拆分。同时考虑到图片之间的间距(gap),使用 calc() 对最终宽度进行修正,避免累计误差

// src/modules/imageGrid.ts

/**
 * 异步更新拼图项宽度
 * 基于图片宽高比计算宽度,使得所有图片高度一致
 */
async function updateGridDimensions(images: HTMLImageElement[], gridItems: HTMLElement[]) {
  const ratios: number[] = [];

  // 获取所有图片的宽高比
  for (const img of images) {
    const ratio = await resolveImageRatio(img);
    ratios.push(ratio);
  }

  // 计算总比例
  const totalRatio = ratios.reduce((sum, r) => sum + r, 0);
  const gapCount = gridItems.length - 1;

  // 设置每张图片的宽度百分比
  gridItems.forEach((item, index) => {
    if (totalRatio > 0) {
      const ratio = ratios[index];
      const percent = (ratio / totalRatio) * 100;
      
      // 使用 calc 计算实际宽度:(比例% * 100) - (gap总宽 * 比例占比)
      // 公式: calc(33.33% - (2 * var(--gap) * 0.3333))
      const widthCalc = `calc(${percent}% - (${gapCount} * var(--photosuite-grid-gap, 4px)) * ${ratio / totalRatio})`;
      
      // 设置 flex-basis 和 max-width
      item.style.flex = `0 0 ${widthCalc}`;
      item.style.maxWidth = widthCalc;
    }
  });
}

这样一来,不管图片比例多么悬殊,它们在拼图中都会自然对齐

高度一致、宽度合理、边缘整齐,同时也避免了任何形式的拉伸或裁剪

布局样式

样式只负责布局:Flex + gap + 响应式间距,没有额外装饰,所有空间都留给图片本身

这里通过 CSS 变量统一管理间距;在移动端则适当缩小 gap,以保证有限屏幕宽度下的视觉紧凑度

// src/styles/image-grid.scss

/* 拼图容器 */
.photosuite-grid {
  --photosuite-grid-gap: 4px;
  
  display: flex;
  gap: var(--photosuite-grid-gap);
  width: 100%;
  margin: 0;
  padding: 0;
  align-items: flex-start;
}

/* 移动端保持拼图布局,但减小间距 */
@media (max-width: 768px) {
  .photosuite-grid {
    --photosuite-grid-gap: 2px;
  }
}

在交互层面,加了点人情味,当鼠标悬停在图片上时(拼图状态),会有一个轻微的放大效果

注:使用该功能无需配置,默认为 imageGrid: true

拼图状态下不显示 EXIF 和标签,它们在编译阶段并未生成

至此,完成

感谢 Claude Code、Gemini、ChatGPT 对项目的大力支持

安装

Photosuite 已发布至 npm,可直接安装:

pnpm add photosuite
# or
npm install photosuite
# or
yarn add photosuite

参考

优化了一下博客的交互细节

2026-01-13 00:39:00

晚上刷 1900 的博客,我忽然想起了 主题配色切换动画

随手输入关键字 “主题” 一搜,果然找到了那篇:给博客主题切换加个动画

看到左上角的发布日期时,不禁有些感叹,原来这已经是半年前的事了

主题切换动画

实现思路与 1900 类似,本质上是利用 View Transitions API 接管视图更新,从而实现遮罩动画

通过 document.startViewTransition 捕获 DOM 快照,再配合 CSS 的 clip-path(裁剪路径)实现平滑过渡:

const transition = document.startViewTransition(() => {
  themeValue = themeValue === "light" ? "dark" : "light";
  setPreference(true);
});

transition.ready.then(() => {
  // 从上至下的裁剪路径
  const clipPath = [
    'inset(0 0 100% 0)', // 开始:底部被完全裁剪
    'inset(0 0 0 0)',    // 结束:完全显示
  ];
  
  document.documentElement.animate(
    { clipPath: clipPath },
    {
      duration: 1000, 	 // 长动画,可能会掉帧
      easing: "ease-out",
      pseudoElement: "::view-transition-new(root)",
    }
  );
});

返回按钮

此外,我还给博客增加了两个功能按钮,灵感借鉴自 SeerSu,他的博客设计非常线性,我很喜欢

  • 返回上一页(如果存在浏览记录),否则返回首页
class="fixed bottom-6 md:bottom-8 z-50 w-10 h-10 rounded-md bg-white/40 dark:bg-gray-900/40 backdrop-blur-md border border-white/50 dark:border-gray-700/50 text-gray-700 dark:text-gray-200 shadow-sm hover:bg-white/70 dark:hover:bg-gray-800/70 hover:scale-110 hover:shadow-lg transition-all duration-300 ease-out translate-y-8 opacity-0 pointer-events-none flex items-center justify-center gap-1"

整体视觉设计上,采用了玻璃风格,悬停时有缩放效果

回到顶部

当页面向下滚动到一定范围,该按钮才会浮现,并自动调整“返回按钮”的位置

// 监听 BackToTop
window.addEventListener("backToTopVisibilityChange", e => {
  // 如果回到顶部按钮出现,返回按钮自动向上移动让出位置
  updateVerticalPosition(e.detail.visible);
});

晨跑,破六

2026-01-07 20:12:00

熟悉我的朋友都知道,我是一个懒人。懒得说话,懒得吃饭

所以像我这样的人,按理说对运动是没什么兴趣的

但耐不住压力大,我急需一种方式来发泄情绪

毕竟,人是可以活活憋死的

杭州城内,我最爱的一条路:南山路

我之前的发泄方式主要是骑车,但现在碳轮坏了,暂时没钱换,于是便转向了登山徒步

不可否认,我非常喜欢这种方式,但徒步太耗时间了,我不可能每天都拨出大半天做这件事

至于跑步,我没想过。仅有的尝试也纯粹是出于好奇。可以说,这辈子我跑过的次数,一个巴掌就能数得过来

2025年12月31日,早晨七点。我实在憋不住了,穿上体能服直接冲了出去

跑出一公里后,我有些后悔没戴帽子和手套。苏堤两岸吹来的冷风冻得我头皮发麻

跑到岳湖附近时,想吐。我手撑着树干呕了几分钟,深感不适,当场掉头慢跑回家了,全程九公里

纯菜逼,回到家后,连饭都吃不下

第二天,也就是 2026 年的第一天。杭州下了一场暴雨,我便没出门

跑步的第二天,差点破六

如果不算年前的那次,从 1 月 2 号到今天,是我连续晨跑的第六天

以前我习惯通宵写代码,而现在为了凌晨能跑起来,作息也调整了

不管手头有什么几把事,统统往后排,每天 12 点前必须睡觉

 咖啡 + 牛奶 + 燕麦

由于晚饭吃得少(或者干脆不吃),凌晨醒来时明显的饿

所以出发前,我都会冲点麦片。这东西挺容易消耗,有饱腹感,即便吃半碗也不影响马上跑步

凌晨 6 点 13 分的湖滨路

插播一个有意思的小插曲:跑步的第二天,经过湖滨银泰时,我貌似和马云擦肩而过

当时我俩距离不过半米,几秒钟后我才反应过来,那个人很可能是他

毕竟马云的相貌还是很出众的,有一定辨识度,可惜当时光线差,他还戴着帽子

回家后我特意查了他近期的行程,啥也没搜到

但如果他当时在杭州,我敢说大概率是他

因为马云一直有户外运动的爱好,之前在杭州爬山、骑车也常被网友偶遇

雷峰夕照

闻子状雷峰,老僧挂偏裻。日日看西湖,一生看不足
—— 张岱《雷峰塔》

今天的天气极好,跑过苏堤时,恰好瞥见了西湖十景之七的“雷峰夕照”

可惜当时站位欠佳,视线被桥两侧的树木挡了大半

不过我也没打算专门跑到桥下去取景,毕竟我是出来跑步的,不是来搞摄影的

日志里的这些照片,每张的拍摄停留时间都不会超过 5 秒

因为我我习惯在跑步过程中提前打开相机,停下的瞬间,让远景对准参考线便按下快门,不过一瞬间

其中不乏有运动过程中拍下的照片,所以有些画面难免会有重影或模糊

实际上我这几天的跑步,都没怎么在中途停下过,也就是所谓的不间断

骑行更是如此,即便是两百公里,我也宁愿在车上慢踩,而不会选择停下

当然,凡事皆有意外

昨天跑得确实有点过火,到 11 公里时,左腿膝盖突然刺痛难忍

眼看只剩最后两公里,我实在不想放弃,便拖着左腿蹦着跑,那种刺痛感真是钻心刺骨,疼得我直咬牙

最后还是认怂了,不过我发现只要不刻意发力,刺痛感就不会有,于是快走了两公里。回到家一看配速:6:13,绝了

凌晨雨跑,环西湖

5 号那天是我人生第一次“雨跑”

刚出门,万松岭的路面就已经全湿了,手掌伸出去能感觉到细密的水滴

就这点降水量想挡住我跑步?门都没有

毕竟我衣服都穿好了,脱是不可能脱的

白堤

雨跑给我的真实感受其实就两个字:湿、重

汗水夹杂着雨水,上半身很快就湿透了,体感并不算舒服

我想,产生这种不适感,也许是因为跑步时注意力还不够集中

不像骑行,如果你行驶中分神,就有可能出事故

所以骑行时,人的感官会高度聚焦,从而忽略身体的琐碎不适

显然,我跑步还没达到那种境界

开水煮西蓝花

除了跑步,最近我还尝试着自己动手做饭

凌晨 5-6 点起床,跑完步刚好是早餐时间

到家后的第一件事通常是点根烟暖暖身,如果出汗多就洗个澡,然后从冰箱拿点能吃的搞一搞

我的早餐口味偏清淡。比如这盘西兰花,什么调料都不放,开水烫一下就可以出锅

鸡蛋 + 火腿 + 韭菜 + 上海青

南德调味料,河南的老乡应该都很熟悉,家乡话叫“南呆”

这大概是我早餐里口味最重的一道菜了,再油腻的我也吃不下

炒鸡蛋出锅时,高压锅里的米粥也熬好了。趁热再去微波炉里热个馒头,一顿饭就齐活了

大概就是这样

破六