🧐如何基于艾宾浩斯记忆曲线设计一个学习规划+定时复习功能

1,309 阅读16分钟

前言

Hello 大家好,我是 oil欧呦,上周写了一篇文章 🤔认真投入一个月做的小程序,能做成什么样子?有人用吗? 介绍了小程序开发了一个月做的一些功能,有幸上了前端热榜的榜二。这周我又给小程序加了一些新功能。大家一起来看看我这周又整了哪些新活儿。

卡盒颜色管理优化

卡盒颜色统一管理,这个需求是我灵光一闪想到的一个功能。在之前的实现中,每一个卡盒已经能够在创建的时候分配随机的一个渐变色,而且用户也可以手动去更改它的渐变色。我提供了一些颜色给大家选择。

这一次改动,我不仅基于单个卡盒维度可以快速修改颜色,我还增加了三个快捷功能,方便大家统一整个文件夹中所有卡盒的颜色。

image.png

色系统一

在选择卡盒颜色的时候,会基于当前选择的颜色,分为冷暖两种色系。

暖色系冷色系
image.pngimage.png

当前卡盒选择颜色后,我们点击对应的冷/暖色系就会弹出一个确认框,就会为当前文件夹中所有的卡盒应用当前色系的渐变色。

image.png

实际的效果就如下图所示,是不是还挺好看的,你更喜欢哪一种呢?

冷色系暖色系
image.pngimage.png

彩虹色

如果点击彩虹色的话,一样会有一个确认弹框,点击后我会为文件夹中的每一个卡盒。按照顺序应用系统中所有的渐变色,最后的效果就像彩虹一样。

彩虹色.gif

统一颜色

顾名思义就是把当前卡盒的颜色应用到所有卡盒上。如果你不喜欢那么花里胡哨的颜色,你就统一一个自己最喜欢的颜色就好了。

image.png

卡盒颜色管理这个功能不是刚需,只是我自己在使用的过程中发现有这么一个功能会让我的使用体验更好,我也相信好看对于一个效率工具来说还是很重要的,每天都要用的东西谁不希望他能够看起来舒舒服服的呢?

学习计划功能

学习功能是我在上一篇文章中说我卡盒小程序的最后一个大块的功能点,这个功能还是花了不少时间去构思它的功能和设计的。下面我给大家介绍一下。

艾宾浩斯记忆曲线

艾宾浩斯记忆曲线,也被称为艾宾浩斯遗忘曲线,是一个描述我们记忆如何随时间推移而逐渐减弱的现象的理论。这个理论是由德国心理学家赫尔曼·艾宾浩斯在1885年提出的。

简单来说,艾宾浩斯记忆曲线揭示了一个规律:我们学习新知识或信息后,遗忘的过程并不是匀速的,而是先快后慢。也就是说,在学习的初期,我们会很快忘记大部分内容,但随着时间的推移,遗忘的速度会逐渐减慢,最终趋于平稳。

基于这个理论我们可以知道,在学习新知识后,应该尽快进行复习,以减缓遗忘的速度。而且,复习的间隔时间也很重要,不能太长也不能太短,要根据遗忘曲线的规律来合理安排。

image.png

制定学习计划

基于记忆曲线的理论,我设计了六个记忆周期,每个周期有不同的复习时间。用户可以在卡盒中制定学习计划来学习其中的卡片。在制定计划时,我们需要选择每天要学习的新卡片数量,

卡盒菜单制定学习计划
image.pngimage.png

在制定学习计划页面,我会根据用户选择的每天要学习的卡片数量去计算最少总复习次数最快完全掌握需要的时间,点击开始计划后,当前卡盒就进入了学习状态。

刚刚开始完成学习
image.pngimage.png

进入学习状态后,卡片列表的顶部会出现一个进度条。底部的操作栏也会出现开始学习的按钮。进度条的计算基于每一张卡片的学习状态。有六个阶段,如果我们全部卡片都没有学习,那么你的进度就是 0%。如果全部卡片都学习到最后一个阶段了,那么进度就是 100%。

每一张卡片的左上角还会有一个进度圆环,最开始的时候,整个圆环是白色的。当我们每张卡片的。学习阶段往后推进的时候这个圆环就会逐渐填满直到最后会变成一整个绿色的圆环。这就代表整张卡片已经学习完了。

在学习的过程中,我们再次打开设置页面,会看到图标变成了查看学习计划。

设置栏查看学习计划
image.pngimage.png

在这个地方,我们看到一些当前学习计划的详细信息,而且也可以随时修改计划或者取消计划。

在卡盒开启学习状态后,我们不论是添加卡片还是删除卡片,他的。整体考核学习进度都是动态计算的。删除卡片相当于你整体的学习进度会往前增加。增加卡片的话。新卡片的学习进度就是第一个阶段,所以你整体的学习进度也会减少。

学习页面

制定学习计划后,还需要有一个专门用来学习的页面,前面我们可以看到开启学习模式后。左下角有一个开始学习的按钮,就会进入学习页面。

学习页面.gif

在学习页面中左上角会展示我们卡盒的名称,还有当前这一轮学习待学的卡片数量和待复习的卡片数量,中间是一张卡片,默认展示的是卡片的正面,底下有记得和不记得两个按钮,无论点击记得还是不记得,我们都会将卡片翻面,查看正确的答案,然后卡片的反面还会有顶部的一个鼓励提示,这个提示的文案我做了很多条,然后他会在其中随机挑选这样子在学习的过程中,他的提示就不会一直重复,显得很枯燥。

当我们选择记得时,我们等于这张卡片已经进入下一个学习阶段了。如果我们选择不记得,这张卡片会向后排,在本轮学习后续继续复习。如果你持续点击,不记得他会一直让你复习直到我们记得了,才会将这张卡片进入下一个阶段。

点击记得和不记得的时候,不只有手指的震动效果,还会有对应的音效,这个音效和顶部的鼓励文案一样,是让这个学习的过程尽量有反馈,让它不那么枯燥,不过这个音效也是支持关闭的。

音效我是在这个网站找的:pixabay.com ,如果你需要点击提示音效的话,也可以在这个网站上找一下,都是免费的。

在每一次完成学习的时候我还做了一个动画效果。有一个奖杯弹一下,然后周围还有一点粒子效果。

完成学习.gif

学习的提示

制定了学习任务之后,不仅在考核里面可以看到你在首页登陆的时候,如果你今天还有待学习的卡片,在首页的顶部就会有一个提示,告诉你还有几个卡盒几张卡片还没有学习。我们点开始学习,它会进入第一个在学习的卡盒去学习。

image.png

卡盒进入学习状态之后,会有一个进度条,和里面的进度条类似,会展示你当前卡盒整体的学习情况。如果有待学习的卡片,还会有一个文案提示你这个卡盒还需要学习。

学习模式如何调试?

像是这种涉及到时间周期的功能,往往都比较难调试,我们不可能真的每次都走完完整的复习流畅,然后等一个多月后再看学习完成的效果,因此我在开发模式下做了一个调试的窗口,右边有个小虫子图标,点击后就可以打开调试操作,我可以很方便的操作当前卡盒的学习周期,测试每个周期的展示效果,并且可以调整复习时间以实现立即开始复习。

image.png

这里有个小技巧,通过 uni.getAccountInfoSync() API 可以获取到小程序当前的账号信息,其中包含了环境信息,我的按钮组件在挂载前就有一个判断:

// 初始化时获取环境信息
onMounted(() => {
  try {
    const accountInfo = uni.getAccountInfoSync()
    isDevelopEnv.value = accountInfo.miniProgram.envVersion === 'develop'
  } catch (e) {
    console.log('获取环境信息失败', e)
  }
})

miniProgram.envVersion 的值有三种情况:

  • release:线上版本
  • develop:开发版
  • trial:体验版

如果你有判断环境执行的逻辑就可以用这个 API 来实现。

自动朗读

我自己在学习卡盒中常用的一个场景是练习一些英语对话,对话的中文是在卡片正面,英文在反面。我希望在点击卡片翻面的时候,他能够自动朗读背面的内容。小红书上也有一个用户,她的反馈是他用来学单词。卡片的正面是英文,反面是中文。希望在卡片滑动的时候自动播放正面的语音。基于这两点需求,我做了自动朗读卡片正面自动朗读卡片背面的功能。

image.png

在大卡片页面点击操作栏中的更多按钮就会看到新的两个朗读设置。默认都是自动朗读正反面的。开启其中的任意一项,不论是点击翻面还是左右滑动,当前的卡片不论是在正面或者反面,并且它是设置了自动播放的它就会自动去播放你的音频。

性能优化

我有一个用户向我反馈,他在一个卡盒中导入了两三千张单词,导致打开大卡片页面以及体验新的学习模式的时候会有些卡顿。 我在最开始设计的时候有尝试过一些边界情况。当时使用了四五百张卡片放在一个卡盒中,列表的渲染有一点点卡顿,但是还可以接受。然后大卡片页面的话也算是可以凑合使用。当时抱着先做功能的心态就没有把性能问题。看得很重。

但当用户实际使用的时候,发现单个卡盒的卡片数量一多,真的是特别卡。尤其是进入卡片列表页面。三四千个节点同时渲染,基本要白屏一两秒钟才能展示出来。

我的小程序由于是个人主体,所以用户的很多数据都是直接存储在本地的,由于不需要频繁地往云端发送请求,所以很多加载策略我没有提前加上。而且本地存储,除了页面的渲染,还有很多数据处理和操作基本上全都是在前端进行的,因此这些运算都是在用户的设备上进行的,如果卡片数量很大的话,性能开销也很大。

为了处理这些问题,我在新的版本中做了很多性能优化。最基础的是卡片列表的滚动加载。即便页面有几百上千张卡片。也是滚到哪加载到哪。

其次是轮播图,如果用原生的轮播图,你是不能动态判断 swiper-item 的渲染的,否则轮播效果就没了,你只能将轮播项里面的内容,根据当前滚动到的位置只展示前后两项。但是真正遇到很长的数组时, swiper-item 元素还是得老老实实渲染几百个。因此我把轮播图组件改成自己实现的,真正达到了展示当前项的前后两项,上千个元素的数组也能正常使用。下面这张图片是一个七百多个卡片的卡盒,给大家看看效果

大卡片滑动.gif

除此以外,一些涉及遍历的操作都想办法降低它的运算复杂度,尽量减少遍历的次数。如果有非常复杂的运算机制,或者说会涉及到所有卡片的一个操作,我就用缓存机制尽量减少很大数据量的处理次数。

还有一个优化方式,把一些运算量比较大的逻辑放到下一个事件循环中去,保障影响 UI 更新的变量先更新。例如说使用 Promise 或者 setTimeout,如果你有使用 lodash 也可以直接使用 delay 函数。这篇文章中不深入讨论,后续我会单独写一篇文章介绍。

为了避免用户的数据量太大导致本地存储不够的问题,我还在设置中增加了当前存储用量的展示。小程序中最大的本地存储上限是 10M,单个 key 是 1M ,是由于我用的是 pinia,持久化时单个 store 的数据都存储在一个 key 中,后续我会把卡盒作分 key 的处理。

image.png

经过一番处理,现在一个卡盒 3000 张卡片在卡片列表中和学习模式基本上都不会卡顿,但是在大卡片页面,由于使用的是轮播图组件,所以在列表比较长的时候还是会卡顿一下。这个我后续会考虑自己做一个虚拟轮播图的组件,不过这个比较花时间。

做再多处理,这也只是一个临时的方法,未来还是考虑把数据全部迁到云端去,或者说还是把数据存储在本地,但是限制单个卡盒中的卡片数量。如果卡片数量过多,我会增加一个 AI 整理卡盒的功能,可以自动把卡片分类整理到不同的卡盒。

代码优化

在学习卡盒小程序的上一个版本中,做的最后一个大功能是关联卡片功能,从那个功能之后,我就开始自己测试把其中的一些坑都踩一踩,然后就写了一些文章给大家介绍了一下,我是想着那个版本稍微公开一下,看看大家的反馈如何,然后我自己就潜心研究小程序的学习功能。

在这个新的版本里,我把很多组件都抽出来复用了,然后把一些犄角旮旯的细节也在优化了一下。因为我很多代码是用cursor写的。在着急实现功能的时候,就没有认真的去看代码写的复用性。后续发现里面有很多冗余的代码,逻辑是 cursor 在修改的时候遗留的,而且组件之间传参过多。其实我很多状态都是写在 store 中的,所以完全可以直接从 store 里面去拿。

代码优化的过程,我也是很多用 cursor 来做,只是我会非常详细的告诉 cursor 我要清理哪些部分的逻辑。我需要把哪个组件给抽离了,尽量让他只做执行的工作,不做创意的工作。

如果一个项目是一个完全不懂代码的小白,让 cursor 去从零开始做的,那么在修 bug 的过程中留下很多的脏代码。 虽然 cursor 会写很多注释逻辑,但是很多时候。他对项目。一个大局层面的了解还是没有你自己清楚。大家也知道,一个项目如果越复杂,里面的脏代码越多,你对这个项目的整体掌控能力就越弱,你对这个项目就越没信心。

像是这次的学习功能,我基本没有让 cursor 参与到设计类的工作,是我自己把整体的功能想清楚了,大概有哪些字段,有哪些结构。然后我先把类型定义写好,再让 cursor 基于我的类型定义和基本逻辑去帮我实现一些功能上的代码。

思考和后续计划

我把学习功能这么重的一个功能,入口放在了卡片列表更多设置里面,其实算是一个比较隐蔽的地方了,我之所以这么做,还是考虑到如果某个用户他并不把这个学习卡盒当做一个纯学习的工具,他可能当做一个记事本或者一个备忘录,或者不喜欢这种定时学习的模式。如果学习功能常驻页面,会使界面繁杂。我希望只有需要的用户才能看到它,而不是将所有主推功能堆砌在页面上。

学习功能现在我只是实现了我预期中最简单的一个版本,后续我还会结合一些 AI 能力实现单选题生成选项和复述题得评分功能, Demo 我已经跑通了,很快也会上线,我不希望把 AI 功能当作噱头,而是希望它能够真正的有用,有的时候强行加入 AI 功能只会让产品的运营成本增加, 如果是傻傻的 AI 那纯属添乱的。

最近有一个小红书上的用户已经学习卡盒来练习雅思的英文对话和单词了,还给我提了好多建议,包括前面提到的性能问题,AI 生成的内容质量,提供自动朗读功能等等,我也很感谢她给我的建议,让我能够发现问题,也能让我的产品帮到她。这是她给我发的截图,卡片内容是 AI 生成的:

1a4e63af79aef18a2dce24437014688.jpg

目前制定学习计划功能已经上线了,后续我会持续迭代,争取把学习卡盒小程序打造成小程序端最好用的卡片学习工具。如果文章对你有帮助,欢迎点个赞,也可以关注一下,后续我会把一些功能的详细实现写成技术文章的。respect~