乐于分享
好东西不私藏

SBTI 赛博人格测试 – 源码深度分析

SBTI 赛博人格测试 – 源码深度分析

项目地址:https://github.com/pingfanfan/SBTI

原创来源:B站UP主 @蛆肉儿串儿


一、项目概述

1.1 项目定位

SBTI(赛博人格测试) 是一个爆火的前端性格测试项目,特点:

  • 开源免费:MIT协议,可自由使用和修改
  • 纯前端实现:无后端依赖,静态部署
  • 数据驱动:所有测试内容在JSON文件中,易于定制
  • 移动优先:响应式设计,手机体验友好

核心数据

  • 30道题:每维度2题
  • 15个维度:5大模型 × 3维度
  • 27种人格:25种标准类型 + 2种特殊类型
  • 曼哈顿距离匹配:科学的向量匹配算法

1.2 项目结构(真实)

SBTI/├── data/                    # 测试数据(核心)│   ├── questions.json       # 30道题库│   ├── dimensions.json      # 15个维度定义│   ├── types.json           # 27种人格类型│   └── config.json          # 评分参数配置├── src/                     # 源代码│   ├── engine.js            # ⭐ 评分算法(核心)│   ├── quiz.js              # 答题流程控制│   ├── result.js            # 结果页渲染│   ├── chart.js             # 雷达图(Canvas API)│   ├── share.js             # 分享功能│   ├── utils.js             # 工具函数│   ├── main.js              # 入口│   └── style.css            # 样式(CSS变量)├── docs/│   └── analysis.md          # 官方数据分析报告├── index.html               # 入口页面├── package.json             # 依赖配置└── vite.config.js           # Vite构建配置

二、核心算法深度解析

2.1 评分流程(基于真实源码)

Step 1: 按维度求和

// src/engine.jsexport function calcDimensionScores(answers, questions) {  const scores = {}  for (const q of questions) {    if (answers[q.id] == nullcontinue    scores[q.dim] = (scores[q.dim] || 0) + answers[q.id]  }  return scores}

输入{ q1: 2, q2: 3, q3: 1, ... }输出{ S1: 5, S2: 4, S3: 6, ... }逻辑:每个维度的2题分值相加


Step 2: 分数转等级

export function scoresToLevels(scores, thresholds) {  const levels = {}  for (const [dim, score] of Object.entries(scores)) {    if (score <= thresholds.L[1]) levels[dim] = 'L'    else if (score >= thresholds.H[0]) levels[dim] = 'H'    else levels[dim] = 'M'  }  return levels}

阈值配置(data/config.json):

{  "scoring": {    "levelThresholds": {      "L": [2, 3],  // 总分2-3 → L(低)      "M": [4, 4],  // 总分4   → M(中)      "H": [5, 6]   // 总分5-6 → H(高)    }  }}

示例

S1维度总分:5 → HS2维度总分:4 → MS3维度总分:3 → L

Step 3: 曼哈顿距离匹配

// 等级转数值const LEVEL_NUM = { L1M2H3 }export function matchType(userLevels, dimOrder, pattern) {  const typeLevels = parsePattern(pattern)  // 解析pattern字符串  let distance = 0  let exact = 0  for (let i = 0; i < dimOrder.length; i++) {    const userVal = LEVEL_NUM[userLevels[dimOrder[i]]] || 2    const typeVal = LEVEL_NUM[typeLevels[i]] || 2    const diff = Math.abs(userVal - typeVal)    distance += diff    if (diff === 0) exact++  }  const similarity = Math.max(0Math.round((1 - distance / 30) * 100))  return { distance, exact, similarity }}

曼哈顿距离公式

distance = Σ |userVal_i - typeVal_i|相似度 = (1 - distance / 30) × 100%

示例计算

用户向量:  [H, H, L, M, H, M, L, L, H, M, H, H, L, M, H]            [3, 3, 1, 2, 3, 2, 1, 1, 3, 2, 3, 3, 1, 2, 3]CTRL类型:  [H, H, H, H, M, H, M, H, H, H, H, H, M, H, M]            [3, 3, 3, 3, 2, 3, 2, 3, 3, 3, 3, 3, 2, 3, 2]差值绝对值:[002111120100111]曼哈顿距离 = 12相似度 = (1 - 12/30) × 100% = 60%精准命中(diff=0的维度)= 7

Step 4: 结果排序与特殊覆盖

export function determineResult(userLevels, dimOrder, standardTypes, specialTypes, options = {}) {  // 1. 计算所有类型的匹配度  const rankings = standardTypes.map((type) => ({    ...type,    ...matchType(userLevels, dimOrder, type.pattern),  }))  // 2. 排序:距离升序 → 精准命中降序 → 相似度降序  rankings.sort((a, b) =>     a.distance - b.distance ||     b.exact - a.exact ||     b.similarity - a.similarity  )  const best = rankings[0]  const drunk = specialTypes.find((t) => t.code === 'DRUNK')  const hhhh = specialTypes.find((t) => t.code === 'HHHH')  // 3. 酒鬼彩蛋覆盖  if (options.isDrunk && drunk) {    return {      primary: { ...drunk, similarity: best.similarityexact: best.exact },      secondary: best,      rankings,      mode'drunk',    }  }  // 4. 傻乐者兜底(相似度<60%)  if (best.similarity < 60 && hhhh) {    return {      primary: { ...hhhh, similarity: best.similarityexact: best.exact },      secondary: best,      rankings,      mode'fallback',    }  }  // 5. 正常匹配  return {    primary: best,    secondary: rankings[1] || null,    rankings,    mode'normal',  }}

2.2 算法流程图

┌─────────────────────────────────────────────────┐│           用户完成30道题                         ││      { q1: 2, q2: 3, ..., q30: 1 }              │└─────────────────┬───────────────────────────────┘                  │                  ▼┌─────────────────────────────────────────────────┐│      Step 1calcDimensionScores()              ││      每维度2题求和 → { S1: 5, S2: 4, ... }     │└─────────────────┬───────────────────────────────┘                  │                  ▼┌─────────────────────────────────────────────────┐│      Step 2scoresToLevels()                   ││      分数转等级 → { S1: 'H', S2: 'M', ... }    │└─────────────────┬───────────────────────────────┘                  │                  ▼┌─────────────────────────────────────────────────┐│      Step 3matchType() × 25种类型             ││      计算曼哈顿距离、精准命中、相似度           │└─────────────────┬───────────────────────────────┘                  │                  ▼┌─────────────────────────────────────────────────┐│      Step 4determineResult()                  ││      排序 + 特殊覆盖                            ││                                                 ││      isDrunk? ──Yes──> DRUNK(酒鬼)            ││          │                                      ││          No                                     ││          │                                      ││      similarity < 60%? ──Yes──> HHHH(傻乐者)  ││          │                                      ││          No                                     ││          │                                      ││      正常匹配 → 最佳类型                         │└─────────────────────────────────────────────────┘

三、数据结构分析

3.1 题目数据结构

{  "id": "q1",  "dim""S1",  "text""我是一只阴暗的老鼠...",  "options": [    { "label": "我哭了。。""value"1 },    { "label": "这是什么。。""value"2 },    { "label": "这不是我!""value"3 }  ]}

字段说明

  • id:题目唯一标识
  • dim:所属维度(S1-S3, E1-E3, A1-A3, Ac1-Ac3, So1-So3)
  • text:题目文本(沙雕风格)
  • options:3个选项,value为分值(1=低,2=中,3=高)

题目特点

  1. 沙雕文案:用夸张、幽默的表达降低心理防御
  2. 情境化:许多题目设置具体情境,而非抽象陈述
  3. 本土化:贴近中国年轻人的语境和网络文化

3.2 人格类型数据结构

{  "code": "CTRL",  "pattern""HHH-HMH-MHH-HHH-MHM",  "cn""拿捏者",  "intro""怎么样,被我拿捏了吧?",  "desc""恭喜您,您测出了全中国最为罕见的人格..."}

字段说明

  • code:人格代码(大写字母,便于传播)
  • pattern:15位维度模式(H/M/L组合,用-分隔每个模型)
  • cn:中文名称
  • intro:一句话开场白(灵魂暴击)
  • desc:详细描述(200-300字)

pattern解析

HHH-HMH-MHH-HHH-MHM│││ │││ │││ │││ │││S1  E1  A1  Ac1 So1S2  E2  A2  Ac2 So2S3  E3  A3  Ac3 So3

3.3 配置数据结构

{  "scoring": {    "levelThresholds": {      "L": [2, 3],      "M": [4, 4],      "H": [5, 6]    },    "fallbackThreshold": 60  },  "display": {    "dimensionsPerRow": 5,    "showRarity": true  }}

关键参数

  • levelThresholds:分数到等级的映射
  • fallbackThreshold:傻乐者兜底阈值(相似度<60%)

四、技术实现亮点

4.1 纯函数设计

engine.js 中的所有评分函数都是纯函数:

  • 无副作用
  • 无DOM依赖
  • 易于测试
  • 易于复用
// 纯函数示例export function calcDimensionScores(answers, questions) {  // 输入 → 输出,无副作用}export function matchType(userLevels, dimOrder, pattern) {  // 输入 → 输出,无副作用}

优势

  • 可独立测试算法正确性
  • 可在其他项目中复用
  • 易于优化和重构

4.2 数据驱动架构

所有测试内容在JSON文件中

data/├── questions.json    # 修改题目├── types.json        # 修改人格类型├── dimensions.json   # 修改维度定义└── config.json       # 修改评分参数

优势

  • 无需改代码:只需修改JSON即可定制
  • 易于维护:数据和逻辑分离
  • 易于扩展:添加新类型只需在types.json中添加对象

4.3 Canvas雷达图实现

chart.js 使用原生Canvas API绘制15维雷达图:

// 维度数值转换const LEVEL_NUM = { L1M2H3 }const normalizedValue = LEVEL_NUM[level] / 3  // 归一化到0-1// Canvas绑制ctx.beginPath()ctx.moveTo(centerX, centerY)for (let i = 0; i < 15; i++) {  const angle = (Math.PI * 2 / 15) * i - Math.PI / 2  const value = normalizedValues[i]  const x = centerX + radius * value * Math.cos(angle)  const y = centerY + radius * value * Math.sin(angle)  ctx.lineTo(x, y)}ctx.closePath()ctx.fillStyle = 'rgba(76, 103, 82, 0.3)'ctx.fill()

优势

  • 无第三方库依赖
  • 性能优异
  • 可完全自定义样式

4.4 Vite构建优化

vite.config.js 配置:

import { defineConfig } from 'vite'export default defineConfig({  base'./',  build: {    outDir'dist',    assetsDir'assets'  }})

优势

  • 开发服务器快速启动
  • HMR(热模块替换)
  • 生产构建优化(代码分割、压缩)

五、心理学逻辑分析

5.1 维度模型设计

5大模型 × 3维度

模型
维度
心理学含义
对应理论
自我模型 (S)
S1 自尊自信
自我价值感
Big Five – 神经质
S2 自我清晰度
自我认知
自我决定理论 – 自主性
S3 核心价值
价值观明确度
存在主义心理学
情感模型 (E)
E1 依恋安全感
亲密关系安全感
依恋理论
E2 情感投入度
感情投入程度
成人依恋理论
E3 边界与依赖
个人边界感
家庭系统理论
态度模型 (A)
A1 世界观倾向
对世界的基本态度
认知心理学
A2 规则与灵活度
对规则的态度
社会心理学
A3 人生意义感
意义感感知
存在主义心理学
行动模型 (Ac)
Ac1 动机导向
成就 vs 风险规避
动机理论
Ac2 决策风格
理性 vs 直觉
认知风格理论
Ac3 执行模式
计划性 vs 灵活性
执行功能理论
社交模型 (So)
So1 社交主动性
社交主动性
Big Five – 外向性
So2 人际边界感
与他人的边界
人际关系理论
So3 表达与真实度
表达真实性
自我呈现理论

5.2 理论借鉴

大五人格模型(Big Five)

对应关系

SBTI维度          Big Five维度─────────────────────────────S1 自尊自信   ←→   神经质(N)的逆向So1 社交主动性 ←→  外向性(E)Ac3 执行模式   ←→  尽责性(C)E1 依恋安全感  ←→  宜人性(A)的部分

简化优势

  • Big Five有30+子维度,SBTI仅15维度
  • 更易理解和传播

依恋理论(Attachment Theory)

情感模型(E)的设计基础

E1 依恋安全感:  - 高分(H)→ 安全型依恋  - 中分(M)→ 矛盾型依恋  - 低分(L)→ 回避型依恋E2 情感投入度:  - 高分(H)→ 焦虑型倾向  - 低分(L)→ 回避型倾向E3 边界与依赖:  - 高分(H)→ 强边界感(回避型)  - 低分(L)→ 弱边界感(焦虑型)

5.3 沙雕文案的心理学原理

为什么沙雕文案有效?

  1. 降低心理防御

    • 严肃问题 → 受测者会”表演”
    • 沙雕问题 → 受测者更真实
  2. 增加参与度

    • 有趣的内容 → 更愿意完成测试
    • 降低中途放弃率
  3. 记忆点

    • 沙雕描述更容易被记住
    • 更容易在社交平台传播

示例对比

严肃版本
SBTI沙雕版本
“我经常感到自卑”
“我是一只阴暗的老鼠,一只爬行的蟑螂…”
“我在感情中缺乏安全感”
“对象超过5小时没回消息,说自己窜稀了,你会怎么想?”
“我做事有明确目标”
“突然某一天,我意识到人生哪有什么他妈的狗屁意义…”

六、统计学逻辑分析

6.1 分数分布

每个维度的分数范围:2-6分(2题×1-3分)

理论分布(假设选项随机选择):

得分概率:2分:1/9  ≈ 11.1%  (1+1)3分:2/9  ≈ 22.2%  (1+2 或 2+1)4分:3/9  ≈ 33.3%  (1+3 或 2+2 或 3+1)5分:2/9  ≈ 22.2%  (2+3 或 3+2)6分:1/9  ≈ 11.1%  (3+3)

实际分布(受题目偏向性影响):

  • 极端题目 → 更多人选中间选项 → M偏多
  • 中性题目 → 分布更均匀

6.2 稀有度计算(官方数据)

基于维度中间值(M)数量

稀有度
M数量
含义
人群占比
SSR
≤3
维度分布极端
1.2-1.8%
SR
4-5
维度分布较极端
3.1-3.8%
R
6-7
维度分布较平衡
4.7-5.3%
N
≥8
维度分布平衡
6.2-7.8%

统计学原理

  • 正态分布中,极端值出现概率低
  • M(中间值)越多 → 越接近”平均值” → 越常见
  • M越少 → 越偏离”平均值” → 越罕见

6.3 曼哈顿距离 vs 欧氏距离

为什么选择曼哈顿距离?

// 曼哈顿距离distance = Σ |userVal_i - typeVal_i|// 欧氏距离distance = √(Σ (userVal_i - typeVal_i)²)

曼哈顿距离优势

  1. 计算简单:无需平方和开方
  2. 可解释性强:每维度的差值直接累加
  3. 不受极端值影响:平方会放大极端值的影响

示例

用户:[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]类型:[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]曼哈顿距离 = 15 × |3-1| = 30欧氏距离 = √(15 × 4) = √60 ≈ 7.75

曼哈顿距离更直观地反映了”总差异”。


七、如何自定义性格测试

7.1 修改题目

编辑 data/questions.json

{  "id": "q1",  "dim""S1",  "text""你的题目文字",  "options": [    { "label": "选项A""value"1 },    { "label": "选项B""value"2 },    { "label": "选项C""value"3 }  ]}

注意事项

  • 每个维度必须有且仅有2道题
  • dim 必须是15个维度之一
  • value 必须是1、2、3

7.2 添加新人格类型

编辑data/types.json

{  "code": "YOUR",  "pattern""HHH-HMH-MHH-HHH-MHM",  "cn""你的类型名",  "intro""一句话开场白",  "desc""详细描述(200-300字)"}

pattern生成方法

  1. 确定目标人格的15维度特征
  2. 按顺序填写H/M/L
  3. -分隔每个模型(3个维度一组)

7.3 调整评分参数

编辑data/config.json

{  "scoring": {    "levelThresholds": {      "L": [2, 3],   // 调整阈值      "M": [4, 4],      "H": [5, 6]    },    "fallbackThreshold": 60  // 调整兜底阈值  }}

7.4 部署自定义版本

方法1:GitHub Pages

# Fork项目# 修改 data/ 目录下的JSON文件# Settings → Pages → Source: GitHub Actions

方法2:本地构建

git clone https://github.com/pingfanfan/SBTI.gitcd SBTInpm installnpm run dev    # 开发npm run build  # 构建# 部署 dist/ 目录

八、项目价值与学习建议

8.1 产品设计亮点

亮点
说明
可借鉴场景
数据驱动
JSON配置,无需改代码
可配置化产品设计
沙雕文案
降低心理防御,提高真实度
年轻人产品、娱乐测试
仪式感
开场白、雷达图、稀有度
结果反馈、成就系统
可分享
生成可分享结果页
社交传播、品牌营销
隐藏彩蛋
酒鬼人格触发机制
游戏化设计、惊喜感

8.2 技术实现亮点

亮点
说明
适用场景
纯函数设计
engine.js无副作用
算法库、核心逻辑
数据与逻辑分离
JSON配置,代码不变
可配置化系统
原生Canvas
无第三方依赖
性能敏感的可视化
Vite构建
快速开发、优化构建
现代前端项目

8.3 学习路径建议

初级(前端基础)

  • 学习项目结构
  • 修改JSON定制内容
  • 部署到GitHub Pages

中级(算法理解)

  • 理解评分算法
  • 理解曼哈顿距离匹配
  • 自定义新人格类型

高级(架构设计)

  • 理解数据驱动架构
  • 学习纯函数设计
  • 扩展新功能(如多语言、数据统计)

8.4 扩展方向

  1. 数据统计

    • 多语言

      • 商业化


        九、总结

        9.1 项目核心价值

        SBTI是一个优秀的开源学习案例,展示了:

        ✅ 如何将心理学理论产品化

        • 借鉴Big Five、依恋理论
        • 简化为15维度
        • 沙雕文案降低门槛

        ✅ 如何设计有传播力的内容

        • 独特的人格代码(CTRL、DEAD)
        • 灵魂暴击开场白
        • 可分享的结果页

        ✅ 如何实现科学匹配算法

        • 曼哈顿距离匹配
        • 多维度排序
        • 特殊覆盖机制

        ✅ 如何构建可维护的架构

        • 数据驱动(JSON配置)
        • 纯函数设计(engine.js)
        • 现代构建工具(Vite)

        9.2 适用人群

        人群
        学习价值
        前端开发者
        数据驱动架构、纯函数设计、Canvas可视化
        产品经理
        用户心理、传播机制、游戏化设计
        心理学爱好者
        人格测试设计、问卷设计、维度建模
        创业者
        产品定位、病毒传播、开源运营

        9.3 关键启发

        1. 简化是门艺术:15维度比30维度更易传播
        2. 文案决定生死:沙雕文案降低门槛、增加趣味
        3. 数据驱动设计:JSON配置,代码不变
        4. 仪式感很重要:结果展示的仪式感决定分享率
        5. 技术服务于体验:简洁的技术实现,优秀的用户体验