使用原生JS实现前端终端效果

文章讲述了如何使用HTML、CSS和JavaScript实现一个带有动态交互的命令输入界面,包括布局设计、输入框与输出框的联动以及处理输入事件和输出调整。关键部分涉及获取最后一个文本节点位置,以动态调整输入框的位置和宽度。
摘要由CSDN通过智能技术生成

布局实现

首先,我们先把HTML结构写出来

<div id="container">
        <div id="terminal">
            <pre id="output"><p id="prompt">C:\Users\Administrator></p></pre>
            <input id="input" type="text" autofocus>
        </div>
    </div>

 然后,再随便搞点样式

#container {

    margin: auto;
    width: 900px;
    height: 600px;
}

#terminal {
    height: 100%;
    overflow-y: auto;
    background-color: black;
    color: #fff;
}

#output,
#output>p {
    display: inline;
}
//命令输入的样式
#input {
    width: 100%;
    color: #fff;
    border: none;
    outline: none;
    display: inline-block;
    background-color: black;
}

效果分析,具体实现

效果分析

首先我们看到效果图

 我们先来分析一下这个效果,首先,布局在上面我们已经搞定了,那缺的就是逻辑处理了

  1. 输入框和输出框的交互:

    • 用户在输入框中输入命令,按下 Enter 键后,模拟执行相应的命令(实际使用可以发送给对应API)。
    • 命令执行的结果以文本形式显示在输出框中。
  2. 输入框位置和宽度的调整:

    • 输入框的位置和宽度会根据输出框的内容动态调整,确保输入框总是位于输出框的下方,以便用户输入。
  3. 点击输出框切换焦点:

    • 点击输出框时,如果没有选中文本,输入框会获得焦点,以便用户可以继续输入命令。
  4. 模拟命令执行和结果显示:

    • 当用户输入命令并按下 Enter 键后,代码会模拟命令执行,并将执行结果显示在输出框中。

难点处理

我们这里的难点就是输入框的位置和宽度的调整,由于需要保证命令输入框的位置总在命令结果或提示符的最后面,这也就意味着,我们需要知道最后一个文本在什么位置,于是,我们可以写出以下代码:

// 获取最后一个文本节点
function getLastTextNode(node) {
    if (node.nodeType === Node.TEXT_NODE) {
        // 如果当前节点是文本节点,返回该节点
        return node;
    }

    // 如果当前节点不是文本节点,遍历其子节点
    const children = node.childNodes;
    for (let i = children.length - 1; i >= 0; i--) {
        const child = children[i];
        // 递归调用,查找最后一个文本节点
        const result = getLastTextNode(child);
        if (result) {
            return result;
        }
    }

    // 如果没有文本节点,返回 null
    return null;
}

这个时候,我们获取到了最后一个文本节点,我们需要获取这个文本节点的位置,不过非常遗憾的是,我们是无法直接获取该文本节点的位置,但是我们可以通过创建另一个文本节点,将其添加到最后一个文本节点的后面,然后再获取我们追加的文本节点的Range对象,通过Range对象,我们就可以获取追加文本节点的位置,在获得位置后,我们就可以计算输入框应该有的宽度,然后设置其宽度。于是就有了下面的代码: 

// 获取页面中的元素
const outputElement = document.getElementById('output');  // 输出结果的元素
const inputElement = document.getElementById('input');    // 输入框元素
const terminal = document.getElementById('terminal');     // 终端区域元素
let textNode = document.createTextNode(" ");               // 用于调整输入框位置的文本节点
// 获取输出区域最后一个文本节点
const lastTextNode = getLastTextNode(outputElement);
if (lastTextNode) {
    // 将文本节点添加到最后一个文本节点的父节点
    lastTextNode.parentNode.appendChild(textNode);
} else {
    // 如果没有文本节点,将文本节点添加到输出区域
    outputElement.appendChild(textNode);
}

//创建Range 对象并设置其起始和终止位置
const range = document.createRange();
range.setStart(textNode, 0);
range.setEnd(textNode, 0);

// 获取文本节点范围的边界矩形
const rect = range.getBoundingClientRect();

// 获取终端区域的边界矩形
const containerRect = terminal.getBoundingClientRect();

// 计算输入框的位置和宽度
const x = rect.x - containerRect.x;
const width = terminal.scrollWidth - x - 10;

// 设置输入框的宽度
inputElement.style.width = `${width}px`;

// 移除文本节点,以免影响布局
textNode.remove();

完整代码

// 获取页面中的元素
const outputElement = document.getElementById('output');  // 输出结果的元素
const inputElement = document.getElementById('input');    // 输入框元素
const terminal = document.getElementById('terminal');     // 终端区域元素
let textNode = document.createTextNode(" ");               // 用于调整输入框位置的文本节点

// 初始化输入框位置
upInput();

// 监听输入框键盘输入事件
inputElement.addEventListener('keyup', function (event) {
    // 检测是否按下 Enter 键
    if (event.key === 'Enter') {
        // 当按下 Enter 键时,获取输入文本,清空输入框,模拟命令执行
        const inputText = inputElement.value;
        inputElement.value = '';
        simulateCommandExecution(inputText);
    }
});

// 监听终端区域点击事件
terminal.addEventListener("click", function (event) {
    // 获取选中的文本
    const selectedText = window.getSelection().toString();
    if (!selectedText) {
        // 如果没有选中文本,聚焦输入框
        inputElement.focus();
    } else {
        // 阻止事件冒泡,以避免影响其他元素的点击事件
        event.stopPropagation();
    }
});

// 调整输入框位置和宽度
function upInput() {
    // 获取输出区域最后一个文本节点
    const lastTextNode = getLastTextNode(outputElement);
    if (lastTextNode) {
        // 将文本节点添加到最后一个文本节点的父节点
        lastTextNode.parentNode.appendChild(textNode);
    } else {
        // 如果没有文本节点,将文本节点添加到输出区域
        outputElement.appendChild(textNode);
    }

    // 创建 Range 对象并设置文本节点范围
    const range = document.createRange();
    range.setStart(textNode, 0);
    range.setEnd(textNode, 0);

    // 获取文本节点范围的边界矩形
    const rect = range.getBoundingClientRect();

    // 获取终端区域的边界矩形
    const containerRect = terminal.getBoundingClientRect();

    // 计算输入框的位置和宽度
    const x = rect.x - containerRect.x;
    const width = terminal.scrollWidth - x - 10;

    // 设置输入框的宽度
    inputElement.style.width = `${width}px`;

    // 移除文本节点,以免影响布局
    textNode.remove();
}

// 获取最后一个文本节点
function getLastTextNode(node) {
    if (node.nodeType === Node.TEXT_NODE) {
        // 如果当前节点是文本节点,返回该节点
        return node;
    }

    // 如果当前节点不是文本节点,遍历其子节点
    const children = node.childNodes;
    for (let i = children.length - 1; i >= 0; i--) {
        const child = children[i];
        // 递归调用,查找最后一个文本节点
        const result = getLastTextNode(child);
        if (result) {
            return result;
        }
    }

    // 如果没有文本节点,返回 null
    return null;
}

// 模拟命令执行
function simulateCommandExecution(command) {
    // 模拟命令执行,生成结果字符串
    let result = "命令:" + command + " 执行成功\r\n";
    // 调用模拟命令执行回调函数
    simulateCommandCallback(result);
}

// 模拟命令执行回调函数
function simulateCommandCallback(result) {
    // 假设下面有非常多数据
    let fragment = document.createDocumentFragment();
    // 获取提示元素
    const promptElement = document.getElementById("prompt");
    // 移除提示元素
    promptElement.remove();
    // 创建段落元素,将结果字符串添加到段落元素中
    let p = document.createElement("p");
    p.innerText = result;
    // 将段落元素和提示元素添加到文档片段中
    fragment.appendChild(p);
    fragment.appendChild(promptElement);
    // 将文档片段添加到输出区域
    outputElement.appendChild(fragment);
    // 调整输入框位置
    upInput();
}

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

界心_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值