在线汉字笔画练习工具(HTML文件版)

在线汉字笔画练习工具(HTML文件版)

Hanzi Writer 是JavaScript 免费开源库,根据汉字书写时按照笔画顺序的特征,可以播放正确笔画顺序的描边动画和练习测试。 支持简体字和繁体字。适用汉字学习者、特别是小学生练习汉字笔画顺序。

可以使用script标签加载CDN:

<script src="https://cdn.jsdelivr.net/npm/hanzi-writer@3.0/dist/hanzi-writer.min.js"></script>

也可以将hanzi-writer.min.js下载到本地,这样就可以离线使用了。

可以根据用户输入的字,给出其笔画动画演示和手写练习。对于确实不支持的汉字也会给出明确的提示。支持的汉字范围:常用汉字(依赖hanzi-writer数据库)。

这个的JavaScript方案有很多优势:

    使用现成的hanzi-writer库;

    完全在浏览器端运行;

    良好的交互体验;

    部署简单(一个HTML文件即可)。

工作流程

 输入一个汉字,点几“确认”,从hanzi-writer库中取出此汉字的笔画信息。当第一次请求某个汉字时,需要从 CDN 下载该汉字的笔画数据文件,所以会较慢。一旦下载后,数据会被浏览器缓存,再次使用同一个字就会较快。

    点击“演示动画”可以看到笔顺演示。

    点击“开始练习”可以进行手写描摹练习。

运行截图如下:

源码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>汉字书写练习</title>
    <script src="https://cdn.jsdelivr.net/npm/hanzi-writer@3.0/dist/hanzi-writer.min.js"></script>
    <style>
        .container {
            text-align: center;
            margin: 20px;
        }
        #character-target-div {
            border: 1px solid #ddd;
            margin: 20px auto;
        }
        .button {
            padding: 10px 20px;
            margin: 5px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .button:hover {
            background-color: #45a049;
        }
        .button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        .input-group {
            margin: 10px;
            display: inline-flex;
            align-items: center;
            gap: 10px;
        }
        input {
            padding: 10px;
            font-size: 16px;
            border: 1px solid #ddd;
            border-radius: 4px;
            width: 50px;
            text-align: center;
        }
        #confirmButton {
            background-color: #007bff;
        }
        #confirmButton:hover {
            background-color: #0056b3;
        }
        #status {
            margin: 10px;
            padding: 10px;
            border-radius: 4px;
            display: none;
        }
        .loading {
            background-color: #fff3cd;
            color: #856404;
            border: 1px solid #ffeeba;
        }
        .error {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        .success {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .loading-spinner {
            display: inline-block;
            width: 20px;
            height: 20px;
            margin-right: 10px;
            border: 3px solid #f3f3f3;
            border-top: 3px solid #3498db;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>在线汉字书写练习工具</h1>
        <div class="input-group">
            <input type="text" id="characterInput" 
                   placeholder="汉字" 
                   maxlength="1" 
                   oninput="onCharacterInput()">
            <button class="button" id="confirmButton" onclick="loadCharacter()">确认</button>
        </div>
        <br>
        <button class="button" id="animateButton" onclick="showAnimation()" disabled>演示动画</button>
        <button class="button" id="quizButton" onclick="startQuiz()" disabled>开始练习</button>
        <br>
        <div id="status"></div>
        <div id="character-target-div"></div>
    </div>

    <script>
        let writer = null;
        let loadingTimeout = null;

        function onCharacterInput() {
            // 输入新字符时禁用功能按钮
            document.getElementById('animateButton').disabled = true;
            document.getElementById('quizButton').disabled = true;
            
            // 清除之前的状态和内容
            hideStatus();
            if (writer) {
                document.getElementById('character-target-div').innerHTML = '';
                writer = null;
            }
        }

        function showStatus(message, type = 'loading') {
            const statusDiv = document.getElementById('status');
            statusDiv.className = type;
            statusDiv.style.display = 'block';
            
            if (type === 'loading') {
                statusDiv.innerHTML = `<div class="loading-spinner"></div>${message}`;
            } else {
                statusDiv.textContent = message;
            }

            if (type === 'error') {
                setTimeout(() => {
                    statusDiv.style.display = 'none';
                }, 3000);
            }
        }

        function hideStatus() {
            document.getElementById('status').style.display = 'none';
        }

        function setButtonsEnabled(enabled) {
            document.getElementById('animateButton').disabled = !enabled;
            document.getElementById('quizButton').disabled = !enabled;
            document.getElementById('confirmButton').disabled = !enabled;
        }

        async function loadCharacter() {
            const character = document.getElementById('characterInput').value;
            if (!character) {
                showStatus('请输入一个汉字', 'error');
                return;
            }
            await initWriter(character);
        }

        // 检查汉字是否成功渲染
        function checkCharacterRendered() {
            const targetDiv = document.getElementById('character-target-div');
            
            // 检查是否有SVG元素
            const svgElement = targetDiv.querySelector('svg');
            if (!svgElement) {
                return false;
            }
            
            // 检查SVG是否有实际的笔画路径
            const pathElements = svgElement.querySelectorAll('path');
            if (pathElements.length === 0) {
                return false;
            }
            
            // 检查路径是否有实际的绘制数据
            for (let path of pathElements) {
                const d = path.getAttribute('d');
                if (d && d.trim() !== '') {
                    return true;
                }
            }
            
            return false;
        }

        async function initWriter(character) {
            setButtonsEnabled(false);
            showStatus('正在加载汉字数据...请稍候');
            
            document.getElementById('character-target-div').innerHTML = '';

            try {
                // 先检查汉字数据是否存在
                const charDataPromise = HanziWriter.loadCharacterData(character);
                const timeoutPromise = new Promise((_, reject) => {
                    loadingTimeout = setTimeout(() => {
                        reject(new Error('加载超时,请重试'));
                    }, 8000);
                });

                let charData;
                try {
                    charData = await Promise.race([charDataPromise, timeoutPromise]);
                    if (!charData || !charData.strokes || charData.strokes.length === 0) {
                        throw new Error('汉字数据不完整');
                    }
                } catch (error) {
                    clearTimeout(loadingTimeout);
                    throw new Error('抱歉,该汉字可能不存在或暂不支持');
                }

                // 创建writer实例
                writer = HanziWriter.create('character-target-div', character, {
                    width: 300,
                    height: 300,
                    padding: 5,
                    showOutline: true
                });

                // 等待渲染完成并检查结果
                await new Promise(resolve => setTimeout(resolve, 1000));

                if (checkCharacterRendered()) {
                    clearTimeout(loadingTimeout);
                    showStatus('加载完成', 'success');
                    setTimeout(hideStatus, 1000);
                    setButtonsEnabled(true);
                    return writer;
                } else {
                    throw new Error('抱歉,该汉字渲染失败或暂不支持');
                }

            } catch (error) {
                clearTimeout(loadingTimeout);
                showStatus(`${error.message}`, 'error');
                document.getElementById('confirmButton').disabled = false;
                console.error('加载失败:', error);
                
                // 清理可能的残留内容
                document.getElementById('character-target-div').innerHTML = '';
                writer = null;
                return null;
            }
        }

        async function showAnimation() {
            if (writer) {
                writer.animateCharacter();
            } else {
                showStatus('请先确认加载汉字', 'error');
            }
        }

        async function startQuiz() {
            if (writer) {
                writer.quiz({
                    onComplete: function(summaryData) {
                        showStatus('练习完成!', 'success');
                        setTimeout(hideStatus, 2000);
                    }
                });
            } else {
                showStatus('请先确认加载汉字', 'error');
            }
        }
    </script>
</body>
</html>

OK!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习&实践爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值