解析effet.js 核心实现原理

175 阅读9分钟

解析effet.js 核心实现原理

在当今的前端开发领域,Vue、React、Angular 等框架几乎成了主流选择。然而,这些框架背后究竟是如何工作的?如果我们从头开始构建一个属于自己的前端框架,会是怎样的体验?本篇博客将从零开始,逐步揭示一个前端框架的基本结构和底层实现原理,逐步探索每一个核心技术背后的逻辑。希望通过这篇文章,能够帮助你更加深入理解前端框架的工作机制,并为你提供自我挑战的动力。

创建文件夹

我们叫做effet

image.png

选择打包工具

目前比较常见的打包工具如:Webpack ,Rollup,Vite,Snowpack,esbuild,Parcel

Vite:以速度快著称,基于原生 ES 模块开发,不需要进行繁重的打包,开发阶段的 HMR(热模块替换)速度更快。Vite 更适合现代 JavaScript 框架的开发,特别是 Vue 和 React 项目。

Rollup:专注于 JavaScript 库和框架的打包,生成较小的文件体积,特别适合构建轻量的前端库或框架。Rollup 支持 Tree Shaking 和多种输出格式(如 ES 模块和 CommonJS),有助于优化包的体积。

Parcel:零配置的打包工具,支持代码分割、热更新、Tree Shaking 等功能。Parcel 会自动处理许多配置需求,适合快速启动和较简单的打包需求。

esbuild:超快的 JavaScript 和 TypeScript 打包工具,基于 Go 开发,处理大项目时速度非常快。虽然 esbuild 的插件生态相对较小,但在大型项目中用作辅助构建工具非常出色。

Snowpack:类似 Vite,不进行捆绑,基于原生 ES 模块加载,适合现代 Web 应用的开发阶段。Snowpack 支持 HMR,允许只更新修改的模块,极大地加快开发速度。

我们选择最常见的

Webpack 进行打包

创建必要的配置

首先创建一个src文件夹,后面用于编写核心代码

创建 package.json

{
  "name": "face-effet",
  "version": "1.3.6",
  "main": "index.js",
  "description": "effet.js 是一个轻量级的人脸样式框架,专注于为网页带来生动的面部动画效果。通过简单的API,开发者可以轻松实现眨眼、张嘴、摇头等动态表情,使用户界面更加互动和生动。effet.js 适用于需要增强用户体验的各种应用场景,特别是在前端项目中集成复杂的人脸动态效果。",
  "private": false,
  "keywords": ["effet", "javascript", "framework", "ui", "animation","face-effet"],
  "author": "typsusan",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/typsusan/effet.git"
  },
  "repository-gitee": {
    "type": "git",
    "url": "https://gitee.com/susantyp/effet.git"
  },
  "scripts": {
    "build": "webpack"
  },
  "files": [
    "effet","README.md","parameter.md","package.json","License"
  ],
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@babel/preset-env": "^7.25.3",
    "babel-loader": "^9.1.3",
    "css-loader": "^7.1.2",
    "css-minimizer-webpack-plugin": "^7.0.0",
    "file-loader": "^6.2.0",
    "mini-css-extract-plugin": "^2.9.1",
    "postcss": "^8.4.41",
    "postcss-loader": "^8.1.1",
    "style-loader": "^4.0.0",
    "ts-loader": "^9.5.1",
    "typescript": "^5.5.4",
    "webpack": "^5.0.0",
    "webpack-cli": "^4.0.0"
  }
}

创建 tsconfig.json,因框架内部需要用到ts相关

{
  "compilerOptions": {
    "outDir": "./effet/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "allowJs": true,
    "lib": ["es6", "dom"],
    "moduleResolution": "node",
    "sourceMap": true,
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

打包配置

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {

    entry: {
        index: './src/index.js',
    },
    output: {
        filename: 'effet.js',
        path: path.resolve(__dirname, 'effet'),
        library: 'effet',
        libraryTarget: 'umd',
        globalObject: 'this',
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.json'],
        alias: {
            '@': path.resolve(__dirname, 'src'),
        },
    },
    optimization: {
        splitChunks: {
            chunks: 'all',
            name: 'common',
        },
        minimizer: [
            `...`,
            new CssMinimizerPlugin(), // 添加CSS压缩插件
        ],
    },
    experiments: {
        asyncWebAssembly: true,
        syncWebAssembly: true,
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                    },
                }
            },
            {
                test: /\.ts$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
            {
                test: /\.css$/, // 处理 CSS 文件
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader'
                ],
            },
        ],
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'effet.css',
        }),
    ],
    mode: 'production',
};

编写核心入口文件夹,以及文件

下面以effet.js做为示例

在这里插入图片描述

入口文件

import { faceElements } from "./core/dom/createFaceElements.js";
import { restart, start, close } from "./core/index";
import def from './core/defaultAssign/assign.js';
import { FACE_TYPE, FACE_SIZE } from "@/components/enums/Constant.ts";
import { cacheAllFiles } from "./core/db/db";
import './core/log/log'

// 引入样式文件
const requireStyles = require.context('./styles', true, /\.css$/);
requireStyles.keys().forEach(requireStyles);

// 初始化函数
export function init(obj) {
    if (!obj?.el) {
        throw new Error("Element not provided. Please pass a valid DOM element to initialize effet.");
    }

    // 初始化基础设置
    def(obj, FACE_TYPE, FACE_SIZE);
    faceElements.init(obj);

    // 缓存完成后再启动
    cacheAllFiles()
        .then(() => {
            start(obj);
        })
        .catch(error => {
            console.error('Cache failed! Please check your network. The system is attempting to cache again for you.', error);
            // 即使缓存失败,也尝试启动
            start(obj);
        });
}

export function cache() {
    cacheAllFiles().then(() => {
        console.log('Cache completed');
    }).catch(error => {
        console.error('Cache failed! Please check your network.', error);
    });
}

// 导出模块
export {
    restart,
    close,
    FACE_TYPE,
    FACE_SIZE
};

export default {
    init,
    close,
    restart,
    FACE_TYPE,
    FACE_SIZE,
    cache
};

index.js 用于初始化和控制某个面部特效(或交互)系统。具体来说,它负责初始化设置、样式引入、缓存资源文件等。以下是代码中各个部分的详细解释:

  1. 模块引入

    • import { faceElements } from "./core/dom/createFaceElements.js";: 引入 faceElements 对象,可能包含创建或管理面部元素的方法。
    • import { restart, start, close } from "./core/index";: 引入控制系统的核心方法,包括 restart(重启)、start(启动)和 close(关闭)。
    • import def from './core/defaultAssign/assign.js';: 引入默认分配设置方法 def,用于初始化基础配置。
    • import { FACE_TYPE, FACE_SIZE } from "@/components/enums/Constant.ts";: 引入两个常量 FACE_TYPEFACE_SIZE,代表面部类型和大小的设置。
    • import { cacheAllFiles } from "./core/db/db";: 引入 cacheAllFiles,用于缓存相关文件资源。
    • import './core/log/log': 引入日志模块,为后续日志记录提供支持。
  2. 引入样式文件

    const requireStyles = require.context('./styles', true, /\.css$/);
    requireStyles.keys().forEach(requireStyles);
    

    这部分代码动态地引入 ./styles 文件夹中的所有 CSS 文件。require.context 是 Webpack 提供的一个方法,用于批量引入模块。此处遍历 requireStyles 中的所有 CSS 文件路径并加载它们。

  3. 初始化函数 init

    export function init(obj) {
        if (!obj?.el) {
            throw new Error("Element not provided. Please pass a valid DOM element to initialize effect.");
        }
    
        def(obj, FACE_TYPE, FACE_SIZE);
        faceElements.init(obj);
    
        cacheAllFiles()
            .then(() => {
                start(obj);
            })
            .catch(error => {
                console.error('Cache failed! Please check your network. The system is attempting to cache again for you.', error);
                start(obj);
            });
    }
    
    • init(obj) 是初始化函数,接受一个对象 obj,其中应该包含一个 DOM 元素 el
    • 检查 obj.el 是否存在,如果不存在则抛出错误。
    • 调用 def(obj, FACE_TYPE, FACE_SIZE); 为对象 obj 设置默认值。
    • 使用 faceElements.init(obj); 初始化面部元素。
    • 调用 cacheAllFiles 缓存文件资源。在缓存完成后调用 start(obj) 启动系统,如果缓存失败,则输出错误信息并仍尝试启动系统。
  4. 缓存函数 cache

    export function cache() {
        cacheAllFiles().then(() => {
            console.log('Cache completed');
        }).catch(error => {
            console.error('Cache failed! Please check your network.', error);
        });
    }
    

    该函数手动触发缓存文件。缓存成功后输出“Cache completed”,失败则输出错误信息。

  5. 导出模块

    export {
        restart,
        close,
        FACE_TYPE,
        FACE_SIZE
    };
    
    export default {
        init,
        close,
        restart,
        FACE_TYPE,
        FACE_SIZE,
        cache
    };
    

    这里分别导出多个方法和变量。export 导出指定模块项;export default 导出一个默认对象,包含 initcloserestartFACE_TYPEFACE_SIZEcache,以便模块在其他文件中被引入和使用。

核心动作基础算法

动作算法入口,可动态选择模块

/**
 * ‘人脸动作’ 集合入口
 * 'Face Action' collection entry
 */
import faceColor from "@/styles/faceColor";
import isEmptyFunctionUtil from "../../util/isEmptyFunctionUtil";

// 使用 require.context 动态加载模块
const actionModules = require.context('./', true, /\.js$/);

export default (appData, results, currentObj, callBackResult, stopRecording, startRecording) => {
    appData.canvasCtx.save();
    appData.canvasCtx.clearRect(0, 0, appData.canvasElement.width, appData.canvasElement.height);
    appData.canvasCtx.drawImage(results.image, 0, 0, appData.canvasElement.width, appData.canvasElement.height);
    if (results.multiFaceLandmarks && results.multiFaceLandmarks.length > 0) {
        if (!appData.predictionState) {
            appData.predictionState = true;
            startRecording();
        }
        if (currentObj.action){
            if (typeof currentObj.action === 'function'){
                faceColor(appData.canvasCtx, results.multiFaceLandmarks, currentObj);
                isEmptyFunctionUtil(currentObj.action,'action')
                currentObj.action(appData,results,currentObj,callBackResult, stopRecording, startRecording)
            }else {
                throw Error("'action' is not a valid function")
            }
        }else {
            // 动态加载模块
            const actionModule = actionModules(`./${currentObj.type}/index.js`);
            if (actionModule) {
                const actionFunction = actionModule.default;
                actionFunction(appData, results, currentObj, callBackResult, stopRecording,startRecording);
            } else {
                console.error(`无法找到模块:${currentObj.type}`);
            }
        }
    } else {
        callBackResult(currentObj, '未检测到人脸...', -2);
        appData.predictionState = false;
        appData.lastNoseX = null;
        appData.noseXChanges = [];
        if (appData.mediaRecorder && appData.mediaRecorder.state !== "inactive") {
            appData.mediaRecorder.stop();
            appData.mediaRecorder = null;
        }
    }
    appData.canvasCtx.restore();
}

代码解析

  1. 模块导入

    import faceColor from "@/styles/faceColor";
    import isEmptyFunctionUtil from "../../util/isEmptyFunctionUtil";
    
    • faceColor 是一个用于设置或渲染人脸颜色的函数或模块。
    • isEmptyFunctionUtil 是一个实用函数,用于检查某个函数是否为空或无效。
  2. 动态加载模块

    const actionModules = require.context('./', true, /\.js$/);
    
    • require.context 是 Webpack 提供的功能,用于动态加载文件夹中的模块。这里加载当前目录(./)中符合 .js 格式的文件,用于动态调用动作模块。
  3. 默认导出函数

    export default (appData, results, currentObj, callBackResult, stopRecording, startRecording) => {
    
    • 这是一个匿名函数,接收多个参数。
      • appData:包含应用状态和画布上下文信息。
      • results:包含人脸检测的结果(如人脸 landmarks)。
      • currentObj:当前的动作对象,包含 action 函数和 type 等信息。
      • callBackResult:回调函数,用于处理无检测到人脸等情况。
      • stopRecordingstartRecording:控制录制状态的函数。
  4. 保存画布上下文并清空画布

    appData.canvasCtx.save();
    appData.canvasCtx.clearRect(0, 0, appData.canvasElement.width, appData.canvasElement.height);
    
    • save():保存画布当前状态,便于稍后恢复。
    • clearRect:清空画布内容,准备绘制新的人脸图像。
  5. 绘制人脸图像

    appData.canvasCtx.drawImage(results.image, 0, 0, appData.canvasElement.width, appData.canvasElement.height);
    
    • 将人脸图像(来自 results.image)绘制到画布上,覆盖整个画布区域。
  6. 检测人脸

    if (results.multiFaceLandmarks && results.multiFaceLandmarks.length > 0) {
    
    • 检查 results.multiFaceLandmarks,判断是否检测到人脸。
    • 如果检测到人脸,则进一步处理,否则执行“未检测到人脸”的逻辑。
  7. 启动录制

    if (!appData.predictionState) {
        appData.predictionState = true;
        startRecording();
    }
    
    • 如果 predictionStatefalse(表明录制未启动),则将其设为 true 并调用 startRecording 开始录制。
  8. 执行动作函数

    if (currentObj.action) {
        if (typeof currentObj.action === 'function') {
            faceColor(appData.canvasCtx, results.multiFaceLandmarks, currentObj);
            isEmptyFunctionUtil(currentObj.action, 'action');
            currentObj.action(appData, results, currentObj, callBackResult, stopRecording, startRecording);
        } else {
            throw Error("'action' is not a valid function");
        }
    }
    
    • 如果

      currentObj.action
      

      存在且是函数,则:

      • 调用 faceColor 对人脸进行着色或效果处理。
      • 使用 isEmptyFunctionUtil 检查 action 是否为空函数。
      • 调用 currentObj.action,传入所有必要参数,执行该动作。
    • 如果 action 不是有效的函数,抛出错误。

  9. 动态加载模块并执行默认动作

    const actionModule = actionModules(`./${currentObj.type}/index.js`);
    if (actionModule) {
        const actionFunction = actionModule.default;
        actionFunction(appData, results, currentObj, callBackResult, stopRecording, startRecording);
    } else {
        console.error(`无法找到模块:${currentObj.type}`);
    }
    
    • 如果 currentObj.action 未定义,则尝试从文件夹 ./${currentObj.type}/ 加载动作模块 index.js
    • 如果模块存在,调用模块的默认导出函数 actionFunction,执行相应动作。
    • 如果模块不存在,输出错误信息提示模块未找到。
  10. 未检测到人脸的处理

    callBackResult(currentObj, '未检测到人脸...', -2);
    appData.predictionState = false;
    appData.lastNoseX = null;
    appData.noseXChanges = [];
    if (appData.mediaRecorder && appData.mediaRecorder.state !== "inactive") {
        appData.mediaRecorder.stop();
        appData.mediaRecorder = null;
    }
    
    • 如果未检测到人脸,调用 callBackResult 提示用户“未检测到人脸”。
    • predictionState 设为 false,重置记录的位置信息。
    • 如果正在录制,通过 mediaRecorder.stop() 停止录制并重置 mediaRecorder
  11. 恢复画布上下文

    appData.canvasCtx.restore();
    
    • 恢复画布状态到 save 时的状态,结束绘图过程。

关键动作函数目录

其中包含

addFace 人脸添加

checkLogin 人脸登录

checkSleep 睡眠检测

clockIn 人脸打卡

image.png

人脸登录核心动作算法

import { distance } from "@/util/distanceUtils";
import faceColor from "@/styles/faceColor";
import { FaceManager } from "@/components/FaceManager.ts";

const NOSE_X_CHANGE_HISTORY_LENGTH = 10;

export default (appData, results, currentObj, callBackResult, stopRecording, startRecording) => {
    const landmarks = results.multiFaceLandmarks[0];
    faceColor(appData.canvasCtx, results.multiFaceLandmarks, currentObj);

    // 获取面部关键点
    const upperLipBottom = landmarks[13];
    const lowerLipTop = landmarks[14];
    const leftEyeTop = landmarks[159];
    const leftEyeBottom = landmarks[145];
    const rightEyeTop = landmarks[386];
    const rightEyeBottom = landmarks[374];
    const noseTip = landmarks[1]; // 鼻尖的标记点

    // 计算动作状态
    const mouthOpen = distance(upperLipBottom, lowerLipTop) > currentObj.threshold.lips;
    const leftEyeOpen = distance(leftEyeTop, leftEyeBottom) > currentObj.threshold.eye;
    const rightEyeOpen = distance(rightEyeTop, rightEyeBottom) > currentObj.threshold.eye;
    const blinked = !(leftEyeOpen && rightEyeOpen);

    let headShaken = false;
    if (appData.lastNoseX !== null) {
        let dx = Math.abs(noseTip.x - appData.lastNoseX);
        appData.noseXChanges.push(dx);

        if (appData.noseXChanges.length > NOSE_X_CHANGE_HISTORY_LENGTH) {
            appData.noseXChanges.shift();
            const maxChange = Math.max(...appData.noseXChanges);
            if (maxChange > currentObj.threshold.headShake) {
                headShaken = true;
            }
        }
    }
    appData.lastNoseX = noseTip.x;

    // 初始化随机动作顺序
    if (!appData.actionsSequence) {
        appData.actionsSequence = ["blink", "mouth", "headShake"].sort(() => Math.random() - 0.5);
        appData.currentActionIndex = 0;
        appData.blinkDetected = false;
        appData.mouthDetected = false;
        appData.headShakeDetected = false;
    }

    const currentAction = appData.actionsSequence[appData.currentActionIndex];

    switch (currentAction) {
        case "blink":
            if (!appData.blinkDetected) {
                callBackResult(currentObj, "请眨眨眼");
                FaceManager.getInstance().updateMessage(0, "请眨眨眼");
                if (blinked) {
                    appData.blinkDetected = true;
                    callBackResult(currentObj, "眨眼检测通过");
                    appData.currentActionIndex++;
                }
            }
            break;

        case "mouth":
            if (!appData.mouthDetected) {
                callBackResult(currentObj, "请张张嘴");
                FaceManager.getInstance().updateMessage(0, "请张张嘴");
                if (mouthOpen) {
                    appData.mouthDetected = true;
                    callBackResult(currentObj, "张嘴检测通过");
                    appData.currentActionIndex++;
                }
            }
            break;

        case "headShake":
            if (!appData.headShakeDetected) {
                callBackResult(currentObj, "请左右摇头");
                FaceManager.getInstance().updateMessage(0, "请左右摇头");
                if (headShaken) {
                    appData.headShakeDetected = true;
                    callBackResult(currentObj, "摇头检测通过");
                    appData.currentActionIndex++;
                }
            }
            break;
    }

    // 检查所有动作是否完成
    if (appData.blinkDetected && appData.mouthDetected && appData.headShakeDetected) {
        FaceManager.getInstance().updateMessage(0, "通过");
        stopRecording(currentObj);
    }
};

定义了一个人脸动作检测函数,主要用于识别眨眼、张嘴和摇头等动作,并依次完成指定的动作顺序。以下是对代码的详细解析:

import { distance } from "@/util/distanceUtils"; 
import faceColor from "@/styles/faceColor";
import { FaceManager } from "@/components/FaceManager.ts";

const NOSE_X_CHANGE_HISTORY_LENGTH = 10;

export default (appData, results, currentObj, callBackResult, stopRecording, startRecording) => {
    const landmarks = results.multiFaceLandmarks[0];
    faceColor(appData.canvasCtx, results.multiFaceLandmarks, currentObj);

    // 获取面部关键点
    const upperLipBottom = landmarks[13];
    const lowerLipTop = landmarks[14];
    const leftEyeTop = landmarks[159];
    const leftEyeBottom = landmarks[145];
    const rightEyeTop = landmarks[386];
    const rightEyeBottom = landmarks[374];
    const noseTip = landmarks[1]; // 鼻尖的标记点
  1. 模块导入
    • distance:用于计算两点之间的距离,帮助判断嘴唇和眼睛的张合状态。
    • faceColor:设置或渲染人脸的颜色效果。
    • FaceManager:用于显示或更新面部动作提示信息。
  2. 面部关键点识别
    • 通过 results.multiFaceLandmarks 获取人脸关键点,定义了一些关键点来检测嘴巴、眼睛和鼻尖的状态。
    // 计算动作状态
    const mouthOpen = distance(upperLipBottom, lowerLipTop) > currentObj.threshold.lips;
    const leftEyeOpen = distance(leftEyeTop, leftEyeBottom) > currentObj.threshold.eye;
    const rightEyeOpen = distance(rightEyeTop, rightEyeBottom) > currentObj.threshold.eye;
    const blinked = !(leftEyeOpen && rightEyeOpen);
  1. 计算动作状态:
  • mouthOpen:通过上下唇的距离与设定的阈值 currentObj.threshold.lips 比较,判断嘴巴是否张开。
  • leftEyeOpenrightEyeOpen:分别计算左右眼的开合状态。
  • blinked:如果两只眼睛都闭上,则表示眨眼。
   let headShaken = false;
    if (appData.lastNoseX !== null) {
        let dx = Math.abs(noseTip.x - appData.lastNoseX);
        appData.noseXChanges.push(dx);

        if (appData.noseXChanges.length > NOSE_X_CHANGE_HISTORY_LENGTH) {
            appData.noseXChanges.shift();
            const maxChange = Math.max(...appData.noseXChanges);
            if (maxChange > currentObj.threshold.headShake) {
                headShaken = true;
            }
        }
    }
    appData.lastNoseX = noseTip.x;
  1. 头部摇动检测:

    • 记录鼻尖的 x 坐标变化量,用 appData.noseXChanges 数组记录变化的历史值。
    • 如果变化量历史记录长度超过 NOSE_X_CHANGE_HISTORY_LENGTH,则移除最旧的记录。
    • 判断鼻尖的最大变化量是否超过阈值 currentObj.threshold.headShake,如果超过,认为进行了头部摇动。
   // 初始化随机动作顺序
    if (!appData.actionsSequence) {
        appData.actionsSequence = ["blink", "mouth", "headShake"].sort(() => Math.random() - 0.5);
        appData.currentActionIndex = 0;
        appData.blinkDetected = false;
        appData.mouthDetected = false;
        appData.headShakeDetected = false;
    }

    const currentAction = appData.actionsSequence[appData.currentActionIndex];
  1. 随机动作顺序 :
  • actionsSequence:保存随机排列的动作顺序,包括眨眼、张嘴和摇头。
  • currentActionIndex:记录当前动作的索引,初始为第一个动作。
  • 初始化 blinkDetectedmouthDetectedheadShakeDetectedfalse
    switch (currentAction) {
        case "blink":
            if (!appData.blinkDetected) {
                callBackResult(currentObj, "请眨眨眼");
                FaceManager.getInstance().updateMessage(0, "请眨眨眼");
                if (blinked) {
                    appData.blinkDetected = true;
                    callBackResult(currentObj, "眨眼检测通过");
                    appData.currentActionIndex++;
                }
            }
            break;

        case "mouth":
            if (!appData.mouthDetected) {
                callBackResult(currentObj, "请张张嘴");
                FaceManager.getInstance().updateMessage(0, "请张张嘴");
                if (mouthOpen) {
                    appData.mouthDetected = true;
                    callBackResult(currentObj, "张嘴检测通过");
                    appData.currentActionIndex++;
                }
            }
            break;

        case "headShake":
            if (!appData.headShakeDetected) {
                callBackResult(currentObj, "请左右摇头");
                FaceManager.getInstance().updateMessage(0, "请左右摇头");
                if (headShaken) {
                    appData.headShakeDetected = true;
                    callBackResult(currentObj, "摇头检测通过");
                    appData.currentActionIndex++;
                }
            }
            break;
    }
  1. 动作检测流程:
  •     switch
    

    语句根据

        currentAction
    

    的值执行相应的检测逻辑:

    • blink:检查 blinked 状态。如果检测到眨眼,将 blinkDetected 设为 true 并调用 callBackResult 传递检测结果,然后移动到下一个动作。
    • mouth:检查 mouthOpen 状态,如果检测到张嘴,更新 mouthDetected 并执行相应操作。
    • headShake:检查 headShaken 状态,如果检测到摇头,更新 headShakeDetected 并执行相应操作。
// 检查所有动作是否完成
if (appData.blinkDetected && appData.mouthDetected && appData.headShakeDetected) {
    FaceManager.getInstance().updateMessage(0, "通过");
    stopRecording(currentObj);
}
};
  1. 检测结束:

    • 如果所有动作(眨眼、张嘴、摇头)都已完成,更新提示为“通过”并调用 stopRecording 结束录制。

核心大概执行流程图

image.png

更多资源