终于,我也能够写出一款代码编辑器

背景

这几天抽空终于写了一款代码编辑器。效果如下图所示:

目前的功能主要有如下:

  1. 代码编辑和运行
  2. 格式化代码
  3. 下载代码
  4. 主题切换
  5. 布局切换
  6. 多语言

项目技术栈主要有如下:

  1. vite构建工具
  2. typescript
  3. monaco-editor
  4. prettier
  5. split.js

当然这款代码编辑器目前主要用于运行html,css,js代码实现的网站应用。

这款代码编辑器主要还是为了服务于我的代码段网站,因为我的代码段网站主要总结的就是常用的html,css,js代码段,我为这些代码段添加了简单的示例,为了方便展示和编辑,所以我写了这款代码编辑器为示例所用。

示例

我们来看一个使用代码编辑器的简单示例。代码如下所示:

html 复制代码
<iframe id="child" scrolling="no" title="code"
        src="https://eveningwater.github.io/code-segment/code-editor/index.html" frameborder="no"
        allowtransparency="true" allowfullscreen="true"></iframe>
css 复制代码
body {
   margin: 0;
}

iframe {
   width: 100%;
   height: 100vh;
   border: none;
}
js 复制代码
const iframe = document.getElementById('child');
const message = [
 {
                type: 'html',
                content: '<h1 id="title">Hello,eveningwater!</h1>'
 },
            {
                type: 'css',
                content: 'h1{color:#2396ef;}'
            },
            {
                type: 'js',
                content: 'document.getElementById("title").onclick = () => {alert("Hello,eveningwater!");}'
            }
];
iframe.addEventListener("load", () => {
    iframe.contentWindow.postMessage(message, "*");
});

以上代码就可以创建一个简单的执行代码的编辑器,如下所示:

你也可以查看在线示例代码

本质上,我们采用postMessage来实现代码通信,而不是存储数据的方式。

复杂示例

下面我们来看一个稍微复杂一点的demo,代码如下:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>postMessage Example - Parent</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        iframe {
            width: 100%;
            height: 100vh;
            border: none;
        }

        code {
            display: none;
        }
    </style>
</head>

<body>
    <iframe id="child" scrolling="no" title="code"
        src="https://eveningwater.github.io/code-segment/code-editor/index.html" frameborder="no"
        allowtransparency="true" allowfullscreen="true"></iframe>
    <code id="html-code">
        <div class="container">
            <h1>商品搜索</h1>
            <div class="search-container">
                <input type="text" id="search-input" class="search-input" placeholder="输入商品名称搜索...">
                <span class="search-icon">🔍</span>
            </div>
            <div id="loading-indicator" class="loading-indicator">搜索中...</div>
            <div class="search-tips">输入商品名称,系统将自动搜索(已应用防抖优化)</div>
            <div id="results-container" class="results-container">
                <div class="no-results">请输入关键词开始搜索</div>
            </div>
        </div>
    </code>
    <code id="css-code">
        * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-family: 'Arial', sans-serif;
        }

        body {
        background-color: #f5f7fa;
        padding: 20px;
        }

        .container {
        max-width: 800px;
        margin: 0 auto;
        background-color: #fff;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        padding: 20px;
        }

        h1 {
        color: #333;
        text-align: center;
        margin-bottom: 20px;
        }

        .search-container {
        position: relative;
        margin-bottom: 20px;
        }

        .search-input {
        width: 100%;
        padding: 12px 15px;
        border: 1px solid #ddd;
        border-radius: 4px;
        font-size: 16px;
        transition: border-color 0.3s;
        }

        .search-input:focus {
        border-color: #4a90e2;
        outline: none;
        }

        .search-icon {
        position: absolute;
        right: 15px;
        top: 50%;
        transform: translateY(-50%);
        color: #999;
        }

        .loading-indicator {
        display: none;
        text-align: center;
        padding: 10px;
        color: #666;
        }

        .results-container {
        border-top: 1px solid #eee;
        padding-top: 15px;
        }

        .result-item {
        display: flex;
        padding: 10px;
        border-bottom: 1px solid #eee;
        transition: background-color 0.2s;
        }

        .result-item:hover {
        background-color: #f9f9f9;
        }

        .result-image {
        width: 80px;
        height: 80px;
        object-fit: cover;
        border-radius: 4px;
        margin-right: 15px;
        }

        .result-info {
        flex: 1;
        }

        .result-name {
        font-weight: bold;
        margin-bottom: 5px;
        color: #333;
        }

        .result-price {
        color: #e74c3c;
        font-weight: bold;
        margin-bottom: 5px;
        }

        .result-description {
        color: #666;
        font-size: 14px;
        }

        .no-results {
        text-align: center;
        padding: 20px;
        color: #666;
        }

        .search-tips {
        margin-top: 10px;
        font-size: 14px;
        color: #999;
        text-align: center;
        }
    </code>
    <script id="js-code">
        // 防抖函数实现
        const debounce = (handler, ms) => {
            let time = null;
            return function (...args) {
                clearTimeout(time);
                time = setTimeout(() => handler.apply(this, args), ms);
            };
        };

        // 模拟商品数据
        const products = [
            {
                id: 1, name: "苹果手机", price: "¥5999", description: "最新款智能手机,拍照性能出色", image:
                    "https://placehold.co/80x80?text=Phone"
            },
            {
                id: 2, name: "笔记本电脑", price: "¥8999", description: "轻薄本,续航持久,性能强劲", image:
                    "https://placehold.co/80x80?text=Laptop"
            },
            {
                id: 3, name: "智能手表", price: "¥1999", description: "健康监测,运动追踪,消息提醒", image:
                    "https://placehold.co/80x80?text=Watch"
            },
            {
                id: 4, name: "无线耳机", price: "¥899", description: "主动降噪,音质清晰,续航长久", image:
                    "https://placehold.co/80x80?text=Earphone"
            },
            {
                id: 5, name: "平板电脑", price: "¥3699", description: "大屏幕,轻便易携带,办公娱乐两相宜", image:
                    "https://placehold.co/80x80?text=Tablet"
            },
            {
                id: 6, name: "智能音箱", price: "¥599", description: "语音控制,音质出色,智能家居控制中心", image:
                    "https://placehold.co/80x80?text=Speaker"
            },
            {
                id: 7, name: "电子书阅读器", price: "¥999", description: "护眼显示,长续航,大容量存储", image:
                    "https://placehold.co/80x80?text=Reader"
            },
            {
                id: 8, name: "游戏主机", price: "¥3899", description: "强大性能,海量游戏,沉浸式体验", image:
                    "https://placehold.co/80x80?text=Console"
            }
        ];

        // 获取DOM元素
        const searchInput = document.getElementById('search-input');
        const resultsContainer = document.getElementById('results-container');
        const loadingIndicator = document.getElementById('loading-indicator');

        // 模拟API请求函数
        const searchProducts = (keyword) => {
            // 显示加载指示器
            loadingIndicator.style.display = 'block';

            // 模拟网络延迟
            setTimeout(() => {
                // 过滤商品
                const filteredProducts = keyword ?
                    products.filter(product =>
                        product.name.toLowerCase().includes(keyword.toLowerCase()) ||
                        product.description.toLowerCase().includes(keyword.toLowerCase())
                    ) : [];

                // 渲染结果
                renderResults(filteredProducts);

                // 隐藏加载指示器
                loadingIndicator.style.display = 'none';

                // 记录搜索日志(实际应用中可能会发送到后端)
                console.log(`搜索关键词: ${keyword}, 找到 ${filteredProducts.length} 个结果`);
            }, 500); // 模拟500ms的网络延迟
        };

        // 渲染搜索结果
        const renderResults = (products) => {
            if (products.length === 0) {
                resultsContainer.innerHTML = '<div class="no-results">没有找到相关商品</div>';
                return;
            }

            let html = '';
            products.forEach(product => {
                html += `
        <div class="result-item">
            <img src="${product.image}" alt="${product.name}" class="result-image">
            <div class="result-info">
                <div class="result-name">${product.name}</div>
                <div class="result-price">${product.price}</div>
                <div class="result-description">${product.description}</div>
            </div>
        </div>
        `;
            });
            resultsContainer.innerHTML = html;
        };

        // 使用防抖函数包装搜索函数
        const debouncedSearch = debounce((keyword) => {
            searchProducts(keyword);
        }, 300); // 300ms的防抖延迟

        // 绑定输入事件
        searchInput.addEventListener('input', (e) => {
            const keyword = e.target.value.trim();
            if (keyword) {
                debouncedSearch(keyword);
            } else {
                // 如果输入框为空,显示初始状态
                resultsContainer.innerHTML = '<div class="no-results">请输入关键词开始搜索</div>';
                loadingIndicator.style.display = 'none';
            }
        });

        // 初始聚焦到搜索框
        searchInput.focus();
    </script>
    <script>
        const iframe = document.getElementById('child');
        const message = [
            {
                type: 'html',
                content: document.getElementById('html-code').innerHTML
            },
            {
                type: 'css',
                content: document.getElementById('css-code').innerHTML
            },
            {
                type: 'js',
                content: document.getElementById('js-code').textContent
            }
        ];
        iframe.onload = function () {
            iframe.contentWindow.postMessage(message, '*');
        }
    </script>
</body>

</html>

效果如下图所示:

你也可以查看在线示例代码

相关推荐
Aphasia31112 分钟前
react必备JavaScript知识点(二)——类
前端·javascript
玖玖passion14 分钟前
数组转树:数据结构中的经典问题
前端
呼Lu噜20 分钟前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
珠峰下的沙砾23 分钟前
Vue3 里 CSS 深度作用选择器 :global
前端·javascript·css
航Hang*26 分钟前
WEBSTORM前端 —— 第2章:CSS —— 第3节:背景属性与显示模式
前端·css·css3·html5·webstorm
wuhen_n27 分钟前
CSS元素动画篇:基于当前位置的变换动画(一)
前端·css·html·css3·html5
拉不动的猪44 分钟前
# 移动端与PC端全屏的处理
前端·javascript·面试
局外人LZ1 小时前
WXT+Vue3+sass+antd+vite搭建项目开发chrome插件
前端·chrome·vue·sass
excel1 小时前
招幕技术人员
前端·javascript·后端
专注VB编程开发20年1 小时前
jss html5-node.nodeType 属性用于表示节点的类型
前端·js