纯前端实现ChatGPT回复效果

1,352 阅读2分钟

ChatGPT火了,客户的心也被ChatGPT俘获了。

客户:你们的问答功能的机器人回复,能像ChatGPT那样一字一字地展示出来,看起来像是有经过思考的样子吗?

产品:好的,我这边跟研发说一下。

我:???

ChatGPT 回复效果是如何实现的?

使用Server-sent events实现。 通常来说,一个网页获取新的数据通常需要发送一个请求到服务器,也就是向服务器请求的页面。使用服务器发送事件,服务器可以随时向我们的 Web 页面推送数据和信息。这些被推送进来的信息可以在这个页面上以 事件 + 数据 的形式来处理。 详情参阅服务器发送事件ChatGPT 打字机消息回复实现原理

纯前端实现回复效果

思路

将获取到的字符串答案,拆分成数组,一个一个往页面上添加。 可以使用 setInterval 或者 requestAnimationFrame 来实现。最终决定使用 requestAnimationFrame ,理由如下: Why is requestAnimationFrame better than setInterval or setTimeout

最终效果

typing

代码实现

两个元素,一个容纳文字,一个模拟光标。

 <div class="wordbox">
      <span id="content">
      </span>
      <span id="caret">
      </span>
  </div>

使用css动画模拟光标闪烁。

#caret {
    display: inline-block;
    width: 10px;
    height: 1rem;
    box-sizing: border-box;
    border-bottom: 3px solid #fff;
    animation: blink-caret 0.2s step-end infinite;
}

.hide {
    display: none !important;
}

@keyframes blink-caret {

    from,
    to {
        opacity: 0;
    }

    50% {
        opacity: 1;
    }
}

使用js动态修改DOM内容

const str = `
  渔舟逐水爱山春,两岸桃花夹去津。
  坐看红树不知远,行尽青溪不见人。
  山口潜行始隈隩,山开旷望旋平陆。
  遥看一处攒云树,近入千家散花竹。
  樵客初传汉姓名,居人未改秦衣服。
  居人共住武陵源,还从物外起田园。
  月明松下房栊静,日出云中鸡犬喧。
  惊闻俗客争来集,竞引还家问都邑。
  平明闾巷扫花开,薄暮渔樵乘水入。
  初因避地去人间,及至成仙遂不还。
  峡里谁知有人事,世中遥望空云山。
  不疑灵境难闻见,尘心未尽思乡县。
  出洞无论隔山水,辞家终拟长游衍。
  自谓经过旧不迷,安知峰壑今来变。
  当时只记入山深,青溪几度到云林。
  春来遍是桃花水,不辨仙源何处寻。
`;
let strArr = str.split('');
const contentDom = document.getElementById('content');
const caret = document.getElementById('caret');
let previousTimeStamp = 0;
let handle;
function typewriter(timestamp) {
    if ((timestamp - previousTimeStamp) > 200) {
        contentDom.innerHTML += strArr.shift();
        previousTimeStamp = timestamp;
    }
    if (strArr.length > 0) {
        handle = requestAnimationFrame(typewriter)
    } else {
        caret.classList.add('hide');
    }
}
function reset() {
    strArr = str.split('');
    contentDom.innerHTML = '';
    caret.classList.remove('hide');
    handle && cancelAnimationFrame(handle);
}
function start() {
    handle = requestAnimationFrame(typewriter);
};
function stopTyping() {
    handle && cancelAnimationFrame(handle);
}

requestAnimationFrame 会返回一个 long 整数,是回调列表中唯一的标识。是个非零值,没有别的意义。传这个值给 cancelAnimationFrame() 可以取消回调函数请求。 (PS: type完一句诗之后,会闪烁好几下没有文字输入是因为换行代来的换行符和空白字符。)


水完了, 哈哈哈