当前位置: 首页 > news >正文

vue实现聊天框自动滚动

需求   

        1、聊天数据实时更新渲染到页面
        2、页面高度随聊天数据增加而增加
        3、竖向滚动
        4、当用户输入聊天内容或者接口返回聊天内容渲染在页面后,自动滚动到底部
        5、提供点击事件操控滚动条上下翻动

环境依赖

        vue:@vue/cli 5.0.8

        taro:v3.4.1


实现方案

方案一:元素设置锚点,使用scrollIntoView() 方法滑动

         Element 接口的 scrollIntoView()  方法会滚动元素的父容器,使被调用 scrollIntoView()  的元素对用户可见

        1、语法
        element.scrollIntoView(); // 等同于 element.scrollIntoView(true)
        element.scrollIntoView(alignToTop); // alignToTop为Boolean 型参数,true/false
        element.scrollIntoView(scrollIntoViewOptions); // Object 型参数
        2、参数
     (1)alignToTop(可选)
        类型:Boolean

        如果为true,元素的顶端将和其所在滚动区的可视区域的顶端对齐。对应的 scrollIntoViewOptions: {block: “start”, inline: “nearest”}。该参数的默认值为true。
        如果为false,元素的底端将和其所在滚动区的可视区域的底端对齐。对应的scrollIntoViewOptions: {block: “end”, inline: “nearest”}。
      (2)scrollIntoViewOptions (可选)
        类型:对象          

        behavior 【可选】
                定义动画的过渡效果,取值为 auto/smooth。默认为 “auto”。
        block 【可选】
                定义垂直方向的对齐, 取值为 start/center/end/nearest 。默认为 “start”。
        inline 【可选】
                定义水平方向的对齐, 取值为 start/center/end/nearest。默认为 “nearest”。
       代码实现如下:

<template><view class="main" id="main"><!--  scroll-y:允许纵向滚动   默认: false | 给scroll-view一个固定高度 |  scroll-into-view: 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素 --><scroll-view class="mainbody" id="mainbody" scroll-with-animation :scroll-y="true" :scroll-into-view="scrollId" style="height:960px;" :enhanced=true scrollIntoViewAlignment="center"@scrolltoupper="upper" @scrolltolower="lower" @scroll="scroll" :scrollWithAnimation="true"><view v-for="(item, index) in contentTypeit.arr" v-bind:key="index":class="['info',  'content-questionBlock']"><view :class="['content']" :id="item.id">{{ item.content}}</view></view><view @click="sendMsg" id="sendMsg"></view><view @click="pageUp" id="pageUp" style="visibility: hidden;"></view><view @click="pageDown" id="pageDown" style="visibility: hidden;"></view></scroll-view></view>
</template><script>
import { ref, reactive, toRaw } from 'vue'export default {setup () {const contentTypeit = reactive({arr: []})const scrollId = ref('id0') //scroll ID值const scrollCursor = ref('id0')const number = ref(0)//https://blog.csdn.net/weixin_43398820/article/details/119963930// 会话内容// 获取对话结果const sendMsg = function () {setContent( 'dfasdfsfsafdsafsafsdfsafsdfsdfdsfsafdsfsadfsafggfdhfhfjgfjhsdgdsfgasfsafdsafsagdhgfhfdhsgdsgdsgdgafsadfdsfdsfsadfhghsdfgsafdsaf')}// 设置对话内容const setContent = function (msg) {let idValue = 'id' + number.valueconst currentObjTypeit = {'content': msg,'id': idValue}let _arr = toRaw(contentTypeit.arr)let _arrTmp = _arr.concat(currentObjTypeit)contentTypeit.arr = _arrTmpnumber.value = number.value + 1;scrollCursor.value = idValue//https://blog.csdn.net/weixin_46511008/article/details/126629361setTimeout(() => {if (number.value !== 0) {let idValueSlide = 'id' + (number.value - 1)document.getElementById(idValueSlide).scrollIntoView({behavior: 'smooth',block: 'center',inline: 'end'})}}, 100);}const scroll = function (e) {// console.log('scroll', e)}const upper = function (e) {// console.log('upper', e)}const lower = function (e) {// console.log('lower', e)}const pageUp = function (e) {console.log(scrollCursor.value)if (scrollCursor.value === undefined || scrollCursor.value === '' || scrollCursor.value.length < 3) {return;}let scrollCursorValue = scrollCursor.value.substring(2);console.log(scrollCursorValue);if (scrollCursorValue >= 1) {scrollCursorValue = scrollCursorValue - 1;scrollCursor.value = 'id' + scrollCursorValue;}setTimeout(function(){if (document.querySelector('#'+ scrollCursor.value) === null) {return;}document.querySelector('#'+ scrollCursor.value).scrollIntoView()}, 200);}const pageDown = function (e) {console.log(scrollCursor.value)if (scrollCursor.value === undefined || scrollCursor.value === '' || scrollCursor.value.length < 3) {return;}let scrollCursorValue = scrollCursor.value.substring(2);console.log(scrollCursorValue);if (scrollCursorValue < contentTypeit.arr.length - 1) {scrollCursorValue = scrollCursorValue -  (-1)scrollCursor.value = 'id' +  scrollCursorValue;}if (scrollCursorValue === contentTypeit.arr.length - 1) {setTimeout(function(){if (document.querySelector('#'+ scrollCursor.value) === null) {return;}document.querySelector('#'+ scrollCursor.value).scrollIntoView(false)}, 500);} else {setTimeout(function() {if (document.querySelector('#'+ scrollCursor.value) === null) {return;}document.querySelector('#'+ scrollCursor.value).scrollIntoView({behavior: "smooth", // 平滑过渡block: "end", // 上边框与视窗顶部平齐。默认值})}, 100);}}return {contentTypeit,scrollId,lower,upper,scroll,sendMsg,pageUp,pageDown,}}
}
</script><style lang="scss">
.main {height: 100%;width: 100%;background-color: rgba(204, 204, 204, 0.32);overflow-x: hidden;overflow-y: auto;
}.mainbody {max-width: 100%;background-size: contain;padding-bottom: 100px;
}.info {display: flex;margin: 10px 3%;
}
.content-question {color: #0b4eb4;background-color: #ffffff;padding-left: 20px;
}.content-questionBlock {align-items: center;
}.content {background-color: #fff;border-radius: 16px;padding: 20px;margin-left: 20px;max-width: 82%;height: 100%;font-size: 36px;font-family: PingFangSC-Medium, PingFang SC;font-weight: 500;color: #0a0a27;line-height: 60px;word-break: break-all;
}
</style>

        效果调试:

      (1)打开浏览器,按下F12进入调试模式;

      (2)在console窗口,多次调用document.getElementById('sendMsg').click(),使得对话内容超出界面高度,可观察到自动滚动效果;

       (3)在console窗口,调用document.getElementById('pageUp').click(),若没有滚动,可调整代码或者调用多次(取决于scrollIntoView()的参数),可观察到向上滚动;接着调用document.getElementById('pageDown').click(),可观察到向下滚动。

        效果图如下:

 方案二: 更改scrollTop取值,进行滚动        

        首先我们需要了解 clientHeightoffsetHeightscrollHeightscrollTop 的概念

        简单介绍:

                clientHeight:网页可见区域高

                offsetHeight:网页可见区域高(包括边线的高)

                scrollHeight:网页正文全文高
                scrollTop:网页被卷去的高

        具体说明:

       (1)clientHeight:包括padding 但不包括 border、水平滚动条、margin的元素的高度。对于inline的元素来说这个属性一直是0,单位px,为只读元素。

        简单来说就是——盒子的原始高度,具体可参考下图:

      (2)offsetHeight:包括padding、border、水平滚动条,但不包括margin的元素的高度。对于inline的元素来说这个属性一直是0,单位px,为只读元素。

        简单来说就是——盒子的原始高度+padding+border+滚动条,具体可参考下图:

       (3)scrollHeight 这个只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。

        简单来说就是——盒子里面包含的内容的真实高度,具体可参考下图:

 

       (4)scrollTop: 代表在有滚动条时,滚动条向下滚动的距离也就是元素顶部被遮住部分的高度。在没有滚动条时 scrollTop==0 恒成立。单位px,可读可设置。

        MDN解释:一个元素的 scrollTop 值是这个元素的内容顶部(被卷起来的)到它的视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那它的 scrollTop 值为0,具体可参考下图:

         实现算法:卷起的高度(scrollTop) = 总的内容高度(scrollHeight) - 聊天区域盒子大小 (offsetHeight);

         代码实现如下:

<template><view class="main" ref="scrollContainer" id="main"><!--  scroll-y:允许纵向滚动   默认: false | 给scroll-view一个固定高度 --><scroll-view class="mainbody" id="mainbody" scroll-with-animation :scroll-y="true"  style="height:960px;" :enhanced=true scrollIntoViewAlignment="center"@scrolltoupper="upper" @scrolltolower="lower" @scroll="scroll" :scrollWithAnimation="true"><view v-for="(item, index) in contentTypeit.arr" v-bind:key="index":class="['info',  'content-questionBlock']"><view :class="['content']" :id="item.id">{{ item.content}}</view></view><view @click="sendMsg" id="sendMsg"></view><view @click="pageUp" id="pageUp" style="visibility: hidden;"></view><view @click="pageDown" id="pageDown" style="visibility: hidden;"></view></scroll-view></view>
</template><script>
import { ref, reactive, toRaw } from 'vue'
import Taro from "@tarojs/taro";export default {setup () {const contentTypeit = reactive({arr: []})const scrollId = ref('id0') //scroll ID值const scrollCursor = ref('id0')const scrollCursorStore = ref(0)// 自动 scrollTop//https://www.cnblogs.com/hmy-666/p/14717484.html  滚动原理与实现//由于插入新的消息属于创建新的元素的过程,这个过程是属于异步的,所以为了防止异步创建元素导致获取高度不准确,我们可以等待一段时间,等元素创建完毕之后再获取元素高度const scrollDownInterval = function () {let idDom = document.getElementById('mainbody')console.log("===================scrollTop,clientHeight,scrollHeight,offsetHeight", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)let currentScrollPosition = scrollCursorStore.value;Taro.nextTick(() => {console.log('scroll start...', idDom.scrollTop)let scrollInterval = setInterval(() => {if ((idDom.scrollTop === idDom.scrollHeight - idDom.offsetHeight) ||(idDom.scrollTop > idDom.scrollHeight - idDom.offsetHeight)) {scrollCursorStore.value = idDom.scrollTopclearInterval(scrollInterval);console.log('scroll end...', idDom.scrollTop)} else {currentScrollPosition =currentScrollPosition + 100;idDom.scrollTop = currentScrollPosition;scrollCursorStore.value = idDom.scrollTopconsole.log('scrolling...', idDom.scrollTop)}}, 200)})}const number = ref(0)//https://blog.csdn.net/weixin_43398820/article/details/119963930// 会话内容// 获取对话结果const sendMsg = function () {setContent( 'dfasdfsfsafdsafsafsdfsafsdfsdfdsfsafdsfsadfsafggfdhfhfjgfjhsdgdsfgasfsafdsafsagdhgfhfdhsgdsgdsgdgafsadfdsfdsfsadfhghsdfgsafdsaf')}// 设置对话内容const setContent = function (msg) {let idValue = 'id' + number.valueconst currentObjTypeit = {'content': msg,'id': idValue}let _arr = toRaw(contentTypeit.arr)let _arrTmp = _arr.concat(currentObjTypeit)contentTypeit.arr = _arrTmpnumber.value = number.value + 1;scrollCursor.value = idValue//https://blog.csdn.net/weixin_46511008/article/details/126629361scrollDownInterval();}const scroll = function (e) {// console.log('scroll', e)}const upper = function (e) {// console.log('upper', e)}const lower = function (e) {// console.log('lower', e)}const pageUp = function (e) {let idDom = document.getElementById('mainbody')console.log("===================", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)let currentScrollPosition = scrollCursorStore.value;scrollCursorStore.value = scrollCursorStore.value - 400if (scrollCursorStore.value < 0) {scrollCursorStore.value = 0;}Taro.nextTick(() => {console.log('scroll start...', idDom.scrollTop)let scrollInterval = setInterval(() => {if ((idDom.scrollTop === scrollCursorStore.value) ||(idDom.scrollTop < scrollCursorStore.value)) {clearInterval(scrollInterval);console.log('scroll end...', idDom.scrollTop)} else {currentScrollPosition =currentScrollPosition - 50;idDom.scrollTop = currentScrollPosition;console.log('scrolling...', idDom.scrollTop)}}, 100)})}const pageDown = function (e) {let idDom = document.getElementById('mainbody')console.log("===================", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)let currentScrollPosition = scrollCursorStore.value;scrollCursorStore.value = scrollCursorStore.value + 400if (scrollCursorStore.value > (idDom.scrollHeight - idDom.offsetHeight )) {scrollCursorStore.value =  idDom.scrollHeight - idDom.offsetHeight;}Taro.nextTick(() => {console.log('scroll start...', idDom.scrollTop)let scrollInterval = setInterval(() => {if ((idDom.scrollTop === scrollCursorStore.value) ||(idDom.scrollTop > scrollCursorStore.value)) {clearInterval(scrollInterval);console.log('scroll end...', idDom.scrollTop)} else {currentScrollPosition =currentScrollPosition - (-50);idDom.scrollTop = currentScrollPosition;console.log('scrolling...', idDom.scrollTop)}}, 100)})}return {contentTypeit,scrollId,lower,upper,scroll,sendMsg,pageUp,pageDown,}}
}
</script><style lang="scss">
.main {height: 100%;width: 100%;background-color: rgba(204, 204, 204, 0.32);overflow-x: hidden;overflow-y: auto;
}.mainbody {max-width: 100%;background-size: contain;padding-bottom: 100px;
}.info {display: flex;margin: 10px 3%;
}
.content-question {color: #0b4eb4;background-color: #ffffff;padding-left: 20px;
}.content-questionBlock {align-items: center;
}.content {background-color: #fff;border-radius: 16px;padding: 20px;margin-left: 20px;max-width: 82%;height: 100%;font-size: 36px;font-family: PingFangSC-Medium, PingFang SC;font-weight: 500;color: #0a0a27;line-height: 60px;word-break: break-all;
}
</style>

        效果调试:

      (1)打开浏览器,按下F12进入调试模式;

      (2)在console窗口,多次调用document.getElementById('sendMsg').click(),使得对话内容超出界面高度,可观察到自动滚动效果;

       (3)在console窗口,调用document.getElementById('pageUp').click(),可观察到向上滚动;接着调用document.getElementById('pageDown').click(),可观察到向下滚动。

        效果图如下:

建议

        方案一由于接口支持,滑动效果更平滑,但是翻页只能调到指定锚点,滑动步长不可控,大部分场景不能满足需求。

        方案二可以自行调整翻页的步长,按需滑动至指定高度,不过滑动动画需要自行实现,看起来卡顿感较强。

        总体来说,建议使用方案二。

参考链接:

        https://blog.csdn.net/weixin_46511008/article/details/126629361

        https://www.cnblogs.com/wq805/p/16399600.html

        https://www.cnblogs.com/hmy-666/p/14717484.html

        Taro 文档

相关文章:

vue实现聊天框自动滚动

需求 1、聊天数据实时更新渲染到页面 2、页面高度随聊天数据增加而增加 3、竖向滚动 4、当用户输入聊天内容或者接口返回聊天内容渲染在页面后&#xff0c;自动滚动到底部 5、提供点击事件操控滚动条上下翻动 环境依赖 vue&#xff1a;vue…...

项目中遇到的一些问题总结(六)

Minio Minio是一个开源的分布式对象存储系统&#xff0c;它使用纠删码技术来保护数据。纠删码技术是一种恢复丢失和损坏数据的数学算法&#xff0c;它将数据分块冗余的分散存储在各个节点的磁盘上&#xff0c;从而提供了一定程度的数据可靠性和冗余性。 在Minio中&#xff0c;…...

Linux线程5——生产消费模型

生产消费模型 1个交易场所:超市 2种角色:生产者/消费者 3种关系:生产者和生产者(竞争关系也叫互斥关系),消费者和消费者(竞争关系同样是互斥关系),生产者和消费者(互斥,同步关系:生产完再消费或消费完再生产)。 以上是生产消费模型遵守的“321”原则。 生产者和消…...

Vue + Springboot 文件上传项目笔记(一)

Vue Springboot 文件上传项目笔记&#xff08;一&#xff09; 前端 使用脚手架创建项目 vue create vue_fileuploaddemo等待命令执行完毕添加 element-ui 组件 E:\java\idea_java_maven\vue_fileuploaddemo>yarn add element-ui yarn add v1.22.19 [1/4] Resolving pac…...

【华为OD机试真题2023B卷 JAVA】座位调整

华为OD2023(B卷)机试题库全覆盖,刷题指南点这里 座位调整 知识点迭代 时间限制:1s 空间限制:256MB 限定语言:C(clang11), C++(clang++11), Java(javac 1.8), Python3(3.9), JavaScript Node(12.18.2), Go(1.14.4) 题目描述: 疫情期间课堂的座位进行了特殊的调整,不能出…...

Python 学习 2022.08.28 周日

文章目录 一、 概述1.1&#xff09; 之前写的文章&#xff1a;1.2) 基础点1.3) 配置1.4) Python2 和 Python3 的区别1.5&#xff09; 相关问题跟踪解决1.6) 其他 一、 概述 1.1&#xff09; 之前写的文章&#xff1a; 【Python大系】Python快速教程《Python 数据库 GUI CGI编…...

WEB自动化测试,一定得掌握的8个核心知识点

​ 编辑 写在前面 使用 cypress 进行端对端测试&#xff0c;和其他的一些框架有一个显著不同的地方&#xff0c;它使用 JavaScript 作为编程语言。 传统主流的 selenium 框架是支持多语言的&#xff0c;大多数 QA 会的 python 和 Java 语言都可以编写 selenium 代码&#xff0…...

期末复习总结!!【MySQL】库和表的基本操作 + 增删改查CURD

文章目录 前言一、数据库的基本操作1, 查看库2, 创建库3, 使用库4, 删除库 二、表的基本操作1, 创建表2, 查看表3, 查看表结构4, 删除表 三、增加(Create)四、查询(Retrieve) (重点)1, 全列查询2, 指定列查询3, 查询字段为表达式4, 指定别名5, 去重6, 排序7, 条件查询7.1, 基本…...

线上问题处理案例:出乎意料的数据库连接池 | 京东云技术团队

导读 本文是线上问题处理案例系列之一&#xff0c;旨在通过真实案例向读者介绍发现问题、定位问题、解决问题的方法。本文讲述了从垃圾回收耗时过长的表象&#xff0c;逐步定位到数据库连接池保活问题的全过程&#xff0c;并对其中用到的一些知识点进行了总结。 一、问题描述…...

有了 IP 地址,为什么还要用 MAC 地址?

MAC地址等价于快递包裹上的收件人姓名。 MAC地址更多是用于确认对方信息而存在的。就如同快递跨越几个城市来到你面前&#xff0c;快递员需要和你确认一下收件人是否正确&#xff0c;才会把包裹交给你一样。 IP66在线查IP地址位置&#xff1a;https://www.ip66.net/?utm-sour…...

ChatGPT 推出 iOS 应用,支持语音输入,使用体验如何?

最近&#xff0c;OpenAI 宣布推出官方 iOS 应用&#xff0c;允许用户随时随地访问其高人气 AI 聊天机器人&#xff0c;此举也打破了近几个月内苹果 App Store 上充斥似是而非的山寨服务的窘境。 该应用程序是 ChatGPT 的首个官方移动应用程序。ChatGPT 软件程序在去年推出后迅速…...

【科普】干货!带你从0了解移动机器人(二)—— 移动机器人硬件组成

移动机器人是一个多功能于一体的综合系统&#xff0c;内容涵盖了传感器技术、自动化技术、信息处理、电子工程等&#xff0c;它集环境感知、动态决策与规划于一体&#xff0c;是目前科学技术发展最活跃的领域之一。移动机器人的各种组件之间需要协同工作才能实现机器人的自主移…...

WIN提权 令牌窃取进程注入

令牌窃取&#xff08;鸡肋玩意 2008包括2008以下&#xff09; 令牌&#xff0c;又叫token&#xff0c;是系统临时产生的秘钥&#xff0c;相当于账号密码&#xff0c;用来决定是否允许此次请求和判断此次请求是属于哪一个用户。 win7一下的版本可以尝试 这里使用msf上自带的令…...

CSS 提高性能的方法,并提供一些实用的技巧和代码示例

CSS 是前端开发中不可或缺的一部分&#xff0c;它负责网页的样式和布局。随着网站规模和复杂度的增加&#xff0c;CSS 的性能也变得越来越重要。本文将介绍 CSS 提高性能的方法&#xff0c;并提供一些实用的技巧和代码示例。 使用压缩后的 CSS 文件 压缩 CSS 文件可以减小文件…...

程序员:面试造火箭,入职拧螺丝?太难了···

刚开始工作的时候&#xff0c;我也想不通这个问题&#xff0c;甚至很鄙视这种现象。后面当了面试官&#xff0c;做到了公司中层管理&#xff0c;也会站在公司以及行业角度去重新思考这个问题。 为什么这种现象会越来越普遍呢&#xff1f;尤其在 IT 行业愈加明显。 面试看的是…...

pg事务:隔离级别历史与SSI

事务隔离级别的历史 ANSI SQL-92定义的隔离级别和异常现象确实对数据库行业影响深远&#xff0c;甚至30年后的今天&#xff0c;绝大部分工程师对事务隔离级别的概念还停留在此&#xff0c;甚至很多真实的数据库隔离级别实现也停留在此。但后ANSI92时代对事物隔离有许多讨论甚至…...

【滑动窗口】【单调队列】个人练习-Leetcode-2373. Largest Local Values in a Matrix

题目链接&#xff1a;https://leetcode.cn/problems/largest-local-values-in-a-matrix/ 题目大意&#xff1a;给出一个N*N矩阵&#xff0c;要求做池化操作&#xff0c;选出每个3*3矩阵的最大值&#xff0c;返回一个(N-2)*(N-2)矩阵 思路&#xff1a;这是个简单题&#xff0c…...

工厂蓝牙定位技术的原理、应用场景、优势及潜在问题

蓝牙定位技术是近年来在工业领域中得到广泛应用的一项技术。随着工业自动化的快速发展和物联网技术的普及&#xff0c;工厂蓝牙定位成为了提高生产效率、优化生产流程和管理的重要工具。本文将详细介绍工厂蓝牙定位技术的原理、应用场景以及其在工业生产中的优势。 首先&#x…...

Linux内核模块编程

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 1 总体设计思路 Linux内核是单体式结构&#xff0c;相对于微内核结构而言&#xff0c;其运行效率高&#xff0c;但是系统的可维护性和可扩展性较差。为此&#xff0c;Linux提供了内核模块&#xff08;module&#xff09;机制&…...

每日一练 | 网络工程师软考真题 Day8

1、某客户端采用ping命令检测网络连接故障时&#xff0c;发现可以ping通127.0.0.1及本机的IP地址&#xff0c;但无法ping通同一网段内其他工作正常的计算机的IP地址。该客户端的故障可能是 。 A&#xff0e;TCP/IP协议不能正常工作 B&#xff0e;本机网卡不能正常工作 …...

Skija与现有Java图形库对比:为什么选择Skija?

Skija与现有Java图形库对比&#xff1a;为什么选择Skija&#xff1f; 【免费下载链接】skija Java bindings for Skia 项目地址: https://gitcode.com/gh_mirrors/sk/skija Skija是一个高质量的Java绑定库&#xff0c;为Java开发者提供了强大的2D图形渲染能力。作为Skia…...

PINCE代码注入实战:.so文件注入完全指南

PINCE代码注入实战&#xff1a;.so文件注入完全指南 【免费下载链接】PINCE Reverse engineering tool for linux games 项目地址: https://gitcode.com/gh_mirrors/pi/PINCE PINCE是一款强大的Linux游戏逆向工程工具&#xff0c;专注于动态链接库注入技术&#xff0c;为…...

【ComfyUI】Qwen-Image-Edit-F2P 在Unity数字人中的应用:驱动3D角色面部表情生成

ComfyUI Qwen-Image-Edit-F2P 在Unity数字人中的应用&#xff1a;驱动3D角色面部表情生成 1. 引言&#xff1a;当AI绘画遇上3D表情动画 想象一下&#xff0c;你正在开发一款游戏或者制作一个虚拟偶像&#xff0c;角色需要做出成百上千种表情——开心的、悲伤的、惊讶的、愤怒…...

深入解析C++中的CRTP(奇异递归模板模式)

深入解析C中的CRTP&#xff08;奇异递归模板模式&#xff09; 在C的模板编程领域&#xff0c;CRTP&#xff08;Curiously Recurring Template Pattern&#xff09;作为一种独特的设计模式&#xff0c;为代码复用和类型安全提供了有效的解决方案。本文将探讨CRTP的基本概念、实现…...

STM32F103C8T6:基于蓝牙指令的舵机角度精确控制

1. 项目背景与应用场景 想象一下这样的场景&#xff1a;早晨醒来&#xff0c;你躺在床上一键遥控窗帘缓缓打开到45度角&#xff0c;让阳光刚好洒在床脚&#xff1b;或者通过手机APP远程调节摄像头云台&#xff0c;让监控视角精确对准门口快递柜。这些看似简单的智能家居功能&am…...

ROS新手必看:5分钟搞定usb_cam相机标定(附棋盘格下载)

ROS实战&#xff1a;从零完成USB摄像头标定的完整指南 在机器人视觉系统中&#xff0c;相机标定是确保测量精度的基础步骤。许多ROS初学者往往在第一步就遇到障碍——要么找不到合适的标定工具&#xff0c;要么被复杂的参数配置搞得晕头转向。本文将带你用最直接的方式完成整个…...

别再死记硬背了!用LabVIEW亲手搭建一个密码验证器,顺便搞懂字符串显示的4种模式

用LabVIEW打造密码验证器&#xff1a;解锁字符串显示的4种实战模式 在虚拟仪器技术的学习中&#xff0c;LabVIEW的字符串处理功能常常让初学者感到困惑。那些抽象的概念和枯燥的理论习题&#xff0c;如果能通过一个有趣的项目来理解&#xff0c;效果会大不相同。今天&#xff0…...

让Windows任务栏呼吸起来:透明美学与智能动态的完美结合

让Windows任务栏呼吸起来&#xff1a;透明美学与智能动态的完美结合 【免费下载链接】TranslucentTB A lightweight utility that makes the Windows taskbar translucent/transparent. 项目地址: https://gitcode.com/gh_mirrors/tr/TranslucentTB 你是否曾盯着Windows…...

ATCODER ABC C题解饺

这&#xff0c;是一个采用C精灵库编写的程序&#xff0c;它画了一幅漂亮的图形&#xff1a; 复制代码 #include "sprites.h" //包含C精灵库 Sprite turtle; //建立角色叫turtle void draw(int d){for(int i0;i<5;i)turtle.fd(d).left(72); } int main(){ …...

Node Modules Inspector性能优化实战:大规模依赖树的可视化处理

Node Modules Inspector性能优化实战&#xff1a;大规模依赖树的可视化处理 【免费下载链接】node-modules-inspector Interactive UI for local node modules inspection 项目地址: https://gitcode.com/gh_mirrors/no/node-modules-inspector Node.js 项目的依赖管理一…...