单词可交互的弧形文本
在一个项目中,要求把少儿读本做成电子教材呈现出来,电子书的排版要求跟纸质书一致。其中,英语书有个需求:书中有些不规则排版的文本(如下图所示),当随书音频播放时,被读到的文本要求高亮。

这个需求偏冷门,所以做了下调研...
一、排版方案选择
文本的不规则排版,网上粗略搜到两种方案:
1)使用SVG的textPath来实现
2)对文本的每个字符计算坐标和旋转角度,然后用绝对定位展示
方案1(SVG),不需要JS计算单个字符的位置,不会出现文字叠加、错位等问题。不过,网上能搜索到的相关代码都只是文本排版,并没有对每个单词绑定事件,所以需要自己对SVG有一定了解,能编写交互功能。
方案2(计算坐标和旋转角度),用基本的JS知识即可实现,难点在于计算比较繁琐,容易出现文字叠加、错位、旋转角度不正确等问题。相关的插件有:vue-arc-text、jQuery.arctext.js等。(前者为Vue组件,配置简便,但不能对单词进行操控;后者依赖jQuery,需要的代码量貌似较多,与Vue格调也不搭,没有尝试的欲望;若自己用vue和js去实现这个思路,也繁琐,作为保底方案,实在不行再执行)
最后,根据个人喜好,优先选择SVG来实现。

二、方案的实现
2.1 基础代码
网上轻易可搜到的环形文字示例代码:
<svg viewBox="0 0 300 150" height="150"><path id="zxxCircle" fill="none" d="M90 75a60 60 0 1 0 120 0a60 60 0 1 0 -120 0z"/><text><textPath href="#zxxCircle">网上找的一段环形文字代码</textPath></text>
</svg>
2.2 相关知识点
上述代码中涉及到的标签有:svg、path、text、textPath,其中path绘制了一条弧线路径(圆形),textPath通过href属性将内部文字排版与path路径绑定。开发时,主要调整的便是path标签的d属性(即弧线绘制)。
1)path
`M/m (x,y)+`: 移动当前位置
`A/a (rx,ry,xr,laf,sf,x,y)+` : 从当前位置绘制弧线到指定位置
rx - (radius-x)弧线所在椭圆的x半轴长
ry - (radius-y)弧线所在椭圆的y半轴长
xr - (xAxis-rotation)弧线所在椭圆的长轴角度
laf - (large-arc-flag)是否选择弧长较长的那一段弧
sf - (sweep-flag)是否选择逆时针方向的那一段弧
x,y - 弧的终点位置
`z`: 路径闭合
2)svg
基本属性:fill(填充)、stroke(描边)、stroke-width、transform(变换),后续我们会用到transform属性。transform可用的属性:
`rotate(<deg>)*` - 旋转
`translate(<x>,<y>)*` - 偏移
`scale(<sx>,<sy>)*` - 缩放
`matrix(<a>,<b>,<c>,<d>,<e>,<f>)*` - 矩阵
3)tspan
textPath中是要排版的文本,因为要对每个单词单独做操控,所以需要用tspan标签对文本做拆分。
2.3 效果实现
真实项目分为管理端和客户端。管理端,可将弧线的每个参数都暴露出来自定义设置;客户端,仅用作展示。
此处,Demo我稍微简化了下设置项。效果如下:

Html关键代码:
<svg :viewBox="svgInfo.viewBox" :transform="svgInfo.transform"><path id="zxxPath" fill="none" :d="svgInfo.d" /><text><textPath href="#zxxPath"><tspan v-for="(v,i) in wordList" :key="i":class="{active: i === tspanActiveIndex}" @click="tspanClickHandler(v,i)"><tspan v-for="(v1,i1) in v.split('')" :key="`${i}_${i1}`" :class="{'active-letter': v1 === letterActive}">{{ v1 }}</tspan> </tspan></textPath></text>
</svg>
JS关键代码:
computed: {wordList () {return this.text.split(' ')},svgInfo () {let result = {viewBox: '0 0 0 0', d: '', transform: 'translate(0,0)'}let x = Math.max(this.startX, this.stopX)let y = Math.max(this.startY, this.stopY)result.viewBox = `0 0 ${x} ${y}`result.transform = `translate(${this.translateX},${this.translateY})`result.d = `M${this.startX},${this.startY} A${this.radius},${this.radius},0,${this.isLong ? 1 : 0},${this.direct},${this.stopX},${this.stopY}`return result}
},
CSS关键代码:
svg {width: 100%;height: 100%;textPath {> tspan {fill: #333;cursor: pointer;.active-letter {fill: #eb6525;}&.active {animation: myanimation .25s linear forwards;.active-letter {fill: inherit;}}}}
}
@keyframes myanimation {from {fill: #333;text-shadow: 0 0 2px #333;}to {fill: red;text-shadow: 0 0 0 yellow;}
}
三、总结
1)参数设置的input标签建议type设置为number,这样用户可以按键盘上的上下箭头来调整数字,结合vue,可以即时看到文字排版效果;
2)svg中标签的style属性和html中存在差异,不支持transform(svg中的transform是单独的属性)。如果想用纯CSS对文字做动画,能操作的只有:font-size、font-style、font-weight、text-shadow、fill等。因为font-size和font-weight会影响后面文本的排版,所以Demo中我是用fill和text-shadow做的简易动效。
相关文章:
单词可交互的弧形文本
在一个项目中,要求把少儿读本做成电子教材呈现出来,电子书的排版要求跟纸质书一致。其中,英语书有个需求:书中有些不规则排版的文本(如下图所示),当随书音频播放时,被读到的文本要求…...
Linux——进程信号(一)
1.信号入门 1.1生活中的信号 什么是信号? 结合实际红绿灯、闹钟、游戏中的"!"等等这些都是信号。 以红绿灯为例子: 一看到红绿灯我们就知道:红灯停、绿灯行;我们不仅知道它是一个红绿灯而且知道当其出现不同的状况…...
centos9 stream在线安装NVIDIA驱动(rockylinux9.4也成功安装nvidia驱动)
Install NVIDIA Drivers on CentOS Stream 9(rockylinux9.4成功) 主板为技嘉mz72-hb2 显卡为4090 一.Disable Secure Boot From the BIOS 二.Enabling the EPEL Repository on CentOS Stream 9 1.update the DNF package repository cache sudo dnf …...
springmvc不同格式的参数解析
参数解析 application/x-www-form-urlencoded格式 这种格式就是传统的表单提交格式,就是一个个的键值对,会进行url编码,使用springmvc接收时使用RequestParam来进行接收,与传入的字段一一对应,此时使用的参数处理器是R…...
Unity3D让BoxCollider根据子物体生成自适应大小
系列文章目录 unity工具 文章目录 系列文章目录unity工具 👉前言👉一、编辑器添加👉二、代码动态添加的方法(第一种)👉三、代码动态添加的方法(第二种)👉四、重新设置模型的中心点👉壁纸分享👉…...
WSL 2 installation is incomplete.
使用的wsl2版本很旧,因此需要手动更新。 https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi...
Servlet的request对象
request对象的继承关系 1.HttpServletRequest接口继承了ServletRequest接口,对其父接口进行了扩展,可以处理满足所有http协议的请求 2.HttpServletRequest和ServletRequest都是接口,不能创建对象,因此在tomcat底层定义实现类并创…...
蓝桥杯-合并数列
小明发现有很多方案可以把一个很大的正整数拆成若干正整数的和。他采取了其中两种方案,分别将它们列为两个数组 {a1, a2, …, an} 和 {b1, b2, …, bm}。两个数组的和相同。 定义一次合并操作可以将某数组内相邻的两个数合并为一个新数,新数的值是原来两…...
《web应用技术》第9次课后作业
一、将前面的代码继续完善功能 1、采用XML映射文件的形式来映射sql语句; 2、采用动态sql语句的方式,实现条件查询的分页。 二、学习git的使用。 1、每个小组将自己的项目上传到gitee,学会协作开发; 2、学会从gitee上拉取项目…...
FRAUDARCatchSync算法简介
参考:https://blog.51cto.com/u_15127663/2778705 1. 背景 Fraudar 要解决的问题是:找出社交网络中最善于伪装的虚假用户簇。虚假用户会通过增加和正常用户的联系来进行伪装,而这些伪装(边)会形成一个很密集的子网络,可以通过定义…...
刷题之将有序数组转换成二叉搜索树(leetcode)
将有序数组转换成二叉搜索树 正常递归,中序遍历 递归经常会把自己绕晕,还是得画图分析 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(null…...
K-means聚类模型教程(个人总结版)
K-means聚类是一种广泛应用于数据挖掘和数据分析的无监督学习算法。它通过将数据点分成K个簇(cluster),使得同一簇内的数据点之间的相似度最大,不同簇之间的相似度最小。本文将详细介绍K-means聚类算法的背景、基本原理、具体实现…...
android怎么告诉系统不要回收
在Android中,如果你想告诉系统不要回收你的应用程序,可以通过设置Activity的属性来实现。你可以设置android:configChanges属性,指定在哪些配置更改时不重新创建Activity。 例如,如果你想指示系统在屏幕方向更改时不要重新创建Ac…...
【FAQ】HarmonyOS SDK 闭源开放能力 —IAP Kit(2)
1.问题描述: 应用内支付IAP Kit和Payment Kit的区别以及适用场景? 解决方案: IAP Kit是四方支付,仅支持在线虚拟商品,如会员,游戏钻石等,双框架支持全球,目前单框架暂时只支持国内…...
ubuntu设置root开机登录,允许root用户ssh远程登录
ubuntu与centos系统不同,默认root开机不能登录。 1、输入一下命令创建root密码,根据提示输入新密码 sudo passwd root 2、打开gdm-autologin文件,将auth required pam_succeed_if.so user ! root quiet_success这行注释掉,这行就…...
Web测试面试题(二)
一:简述HTTP协议的状态码包含哪些? 2XX,表示成功 3XX,表示重定向 4XX,表示客户端错误 5XX,表示服务器错误 二:HTTP和HTTPS的区别? 《1》安全性上的区别: HTTPS&#x…...
VBA宏指令写的方法突然不能用了
背景:项目组有个自动化测试项目,实在excel中利用VBA开发的;时间比较久远,我前面的哥们走后,这个软件一直在用,最近系统不知道是不是更新的缘故;有些代码除了问题; 先上源码: Dim Conn As Object, Rst As Object Dim sqlStr$ Dim str_start_SN$, str2$ str_start_SN …...
第13章 Python建模库介绍
以下内容参考自https://github.com/iamseancheney/python_for_data_analysis_2nd_chinese_version/blob/master/%E7%AC%AC05%E7%AB%A0%20pandas%E5%85%A5%E9%97%A8.md 《利用Python进行数据分析第2版》 用以学习和记录。 本书中,我已经介绍了Python数据分析的编程基…...
IP学习——ospf1
OSPF:开放式最短路径优先协议 无类别IGP协议:链路状态型。基于 LSA收敛,故更新量较大,为在中大型网络正常工作,需要进行结构化的部署---区域划分、ip地址规划 支持等开销负载均衡 组播更新 ---224.0.0.5 224.0.0.6 …...
别说废话!说话说到点上,项目高效沟通的底层逻辑揭秘
假设你下周要在领导和同事面前汇报项目进度,你会怎么做?很多人可能会去网上搜一个项目介绍模板,然后按照模板来填充内容。最后,汇报幻灯片做了 80 页,自己觉得非常充实,但是却被领导痛批了一顿。 这样的境…...
全域态势数字孪生,筑牢楼宇长效安全透明防护屏障
全域态势数字孪生,筑牢楼宇长效安全透明防护屏障副标题:全要素三维动态实时复刻楼宇实景,依托无感全域人员感知、多机位跨镜联动追踪、身体指纹唯一身份归档,异常行为、区域滞留、安全隐患提前透明预警处置一、方案概述伴随城市高…...
ElevenLabs匈牙利语音API响应延迟飙升300%?内网穿透+CDN缓存+匈牙利语音素预加载三阶优化方案
更多请点击: https://intelliparadigm.com 第一章:ElevenLabs匈牙利文语音API响应延迟飙升300%的现象复现与根因定位 近期多位开发者反馈,ElevenLabs API 在处理匈牙利语(hu-HU)文本转语音请求时,平均端到…...
AI攻防时间差:当漏洞发现速度碾压修复速度— 聚焦技术核心
AI攻防时间差:当漏洞发现速度碾压修复速度 — 聚焦技术核心 引言:当两个世界碰撞 2026年5月,对于网络安全领域而言,是一个具有分水岭意义的月份。 一边是360人工智能安全研究院在5月12日发布的重磅报告,首次提出**“AI…...
Odrive 0.5.5 固件启动流程详解:从USB初始化到电机线程就绪,新手避坑指南
ODrive 0.5.5 固件启动全流程拆解:从硬件初始化到电机控制线程就绪 当一块崭新的ODrive开发板首次通电时,固件究竟在后台执行了哪些精密操作?本文将深入RTOS内核,以时间轴方式还原从rtos_main入口到双轴电机线程就绪的完整启动过程…...
避开这些坑,你的YOLO论文才能发得快!目标检测老鸟的实战避坑与效率工具清单
YOLO论文高效产出指南:目标检测老手的避坑策略与工具链实战 实验室的灯光在凌晨三点依然亮着,屏幕上YOLOv8的loss曲线却像心电图一样毫无规律地跳动着。这已经是本周第三次复现顶会论文失败,而距离截稿日期只剩三周。如果你也经历过这种"…...
别再手动拼接URL了!若依集成JimuReport报表,一个优雅的Token传递方案
若依系统与JimuReport深度集成:Token安全传递的架构实践 在当今企业级应用开发中,报表功能是不可或缺的核心模块,而如何将第三方报表系统无缝集成到现有框架中,同时确保认证体系的安全性与一致性,一直是开发者面临的挑…...
ROFL-Player:英雄联盟回放时光机,一键穿越所有版本
ROFL-Player:英雄联盟回放时光机,一键穿越所有版本 【免费下载链接】ROFL-Player (No longer supported) One stop shop utility for viewing League of Legends replays! 项目地址: https://gitcode.com/gh_mirrors/ro/ROFL-Player 还在为英雄联…...
CTP接口实战:从零构建量化交易系统(附完整源码)
1. CTP接口入门:量化交易的第一块基石 第一次接触CTP接口时,我盯着那堆C代码发呆了半小时——这玩意儿比我想象的复杂多了。后来才发现,其实把它理解成期货市场的普通话就简单了。就像我们用普通话跟人交流,程序用CTP接口跟期货交…...
从‘尺子刻度’到‘信号保真’:用Python仿真带你直观理解ADC的INL、DNL和SNDR到底在说什么
从‘尺子刻度’到‘信号保真’:用Python仿真带你直观理解ADC的INL、DNL和SNDR到底在说什么 在数字信号处理的世界里,模数转换器(ADC)扮演着将连续模拟信号转换为离散数字信号的关键角色。但对于许多软件开发者或跨领域学习者来说,ADC的性能参…...
小红书运营开源技能库:从社区共建到数据驱动的实战指南
1. 项目概述:小红书运营技能库的诞生与价值最近几年,我身边不少朋友和同行都在讨论一个现象:小红书的运营,好像越来越“卷”了。从早年的美妆、穿搭,到后来的探店、母婴,再到现在的知识付费、职场成长&…...
