本文由 IMWeb 首发于 IMWeb 社区网站 imweb.io。点击阅读原文查看 IMWeb 社区更多精彩文章。
需求简介
腾讯企鹅辅导在学生上课结束后推送“学习报告”,是课程所提供的一项重要服务。家长在“学习报告”中能查看孩子上课时间及互动情况,答题及掌握知识点,作业考试分数,班级排名等诸多数据,继而让学生家长及时掌握孩子的学习情况。
此次改版升级是针对旧学习报告的的数据和展示进行的一次优化:增加考试模块、知识点采用更简单的表达形式、在视觉交互上更加年轻活泼、并运用了更多数据图表可视化在其数据展示中。
考试模块-数据图表可视化的应用
1.数据可视化组件库的选择及应用
在考试模块中,需要展示学生成绩变化趋势的曲线图,而这需要用到第三方的可视化组件库,继而快速回忆起比较知名的几款:国外的HighChart,百度家的Echart,阿里的AntV(移动端F2)等。当然也希望腾讯有一天也能有同样知名好用的的可视化组件库。
在选择可视化组件库时,我们主要考虑以下几点:1.能够良好支持移动端且轻量。2.支持React。3.具备足够自由的可定制化配置样式的能力。
其中第三点尤其重要,因为这里要准确还原交互视觉(不得不说我们交互和视觉给的要求很高)。根据经验,纵使强大的可视化组件库配置非常繁多,但往往可配置的内容太多,根本找不到用什么配置项达到目的,而且一些配置相互影响,变化很多。
最终我们发现并使用了Recharts。它是一个使用React和D3构建的Redefined图表库。具备以下特性:
- 支持React组件,声明式的标签,写图表和写 HTML 一样简单。
- 原生SVG支持,依赖于轻量级的 D3 子模块构建 SVG 元素。
- 接口式的 API,解决各种个性化的需求。
以下是部分需求代码,展示了其用法和特性:
代码语言:javascript复制<ResponsiveContainer width="100%" height={200}> <LineChart data={data}> <CartesianGrid horizontal={false} strokeDasharray="2 3" /> <Line type={lineStyle} dataKey="avgScore" stroke="#CCCCCC" fill="#CCCCCC" label={ <CustomizedLabel direction="down" data={data} relateKey="actualScore"/> } isAnimationActive={false} /> <Line type={lineStyle} dataKey="actualScore" stroke="#08CB6A" fill="#08CB6A" label={<CustomizedLabel data={data} relateKey="avgScore" />} isAnimationActive={false} /> <Legend align="left" verticalAlign="top" iconSize={4} iconType="rect" height={36} formatter={(value) => { return { actualScore: '我的成绩', avgScore: '班级平均分' }[value]; }} wrapperStyle={{ left: -13, fontSize: 12, }} /> <XAxis dataKey="name" padding={{ left: padding, right: padding }} axisLine={false} tickLine={false} /> <YAxis domain={[-8, 108]} hide /> </LineChart> </ResponsiveContainer>
除了样式配置项外,还提供了诸如“strokeDasharray”贴近原生SVG的配置项。对于熟悉SVG的同学就能能很准确写图形样式了。
2. 如何画好一根曲线[贝塞尔曲线]
说道贝塞尔曲线,前端的同学很容易想到的是CSS transition中的cubic-bezier,一般是起始点和两个控制点 来生成两点间的一条曲线,也就是常用三阶贝塞尔曲线。关于贝塞尔曲线就不再赘述了,其原理和SVG中Path中贝塞尔曲线的使用,可查阅下面两篇文章。贝塞尔曲线原理 SVG Path 曲线
OK,根据需求,我们考试成绩已经确定两个点了,那么这根曲线到底具备怎样的“性格”,弯一点还是平滑一点?但是这需要和视觉的同学反复调整得出一个让她满意的“参数”。当然如果要做到完全满意,可能还要针对不同情况计算不同的参数。
下面代码为:通过D3 shape(可视化的图形基元),除了终点,两个控制点的x值通过参数设置。将其实例作为props 的type值传入Recharts中的 <Line/>
中,即可得到想要的曲线。
BezierLineShape.prototype = { lineStart() { this._x0 = this._x1 = this._y0 = this._y1 = this._t0 = NaN; this._point = 0; console.log('lineStart', this._line, this._point); }, lineEnd() { console.log('lineEnd', this._line, this._point); }, point(x, y) { console.log('point', x, y, this._line); (x = x), (y = y); if (x === this._x1 && y === this._y1) { return; } switch (this._point) { case 0: { this._point = 1; this._x1 = x; this._y1 = y; this._context.moveTo(x, y); break; } case 1: { const mint = (x - this._x1) * 0.35;//此为控制点位置参数 const x1 = this._x1 mint; const y1 = this._y1; const x2 = x - mint; const y2 = y; this._x1 = x; this._y1 = y; console.log('bezierCurveTo', x1, y1, x2, y2, x, y); this._context.bezierCurveTo(x1, y1, x2, y2, x, y); break; } default: break; } },};
最后效果图:
基于SVG做的客制化修改
Scalable Vector Graphics,意思为可缩放的矢量图形,它基于XML,是一种开放标准的矢量图形语言。recharts提供基于react组件的写法,去写可定制化svg图形。比如下面:用组件svg 来定制的Label的位置样式。
代码语言:javascript复制export default class CustomizedLabel extends React.PureComponent { static defaultProps = { direction: 'up', // stroke: '#777', }; render() { const { x, y, stroke, value, direction, index, relateKey, data } = this.props; let settedDirect = direction; try { const relateValue = data[index][relateKey]; if (value > relateValue) { settedDirect = 'up'; } else if (value < relateValue) { settedDirect = 'down'; } } catch (e) { // BJ_Report } const dy = settedDirect === 'up' ? -10 : 18; return ( <text x={x} y={y} dy={dy} fill={stroke} fontSize={14} textAnchor="middle"> {value} </text> ); }}
学习回顾-轮播柱状图结合实现
在学习回顾模块,用户可以左右滑动/点击柱状图,来切换不同课程信息展示。很显然可以通过一个轮播组件来实现,但是这个模块还具备柱状图的展示。要选择一个兼具轮播和图表的组件,还要保证两者的功能和样式都可按需求定制。显然这并不容易,即便存在这样组件也要花上不少时间去寻找和筛选。
这时就要权衡,到底是在一个轮播组件添加图表,还是改造图表组件为轮播。这里我选择基于轮播组件来写里面的柱状图的这个方案。原因是:这里的柱状图并不复杂,可以用dom css样式来实现,并且正好实现样式定制化的需求。虽然图表组件(比如antV的F2)也提供类似滑动图表的功能,但是由于轮播不是它主要特性,诸如多item展示以及居中item选中等特性,改起来也不容易。
确定在轮播组件实现柱状图方案后,发现在实现仍有难点:第一个item的左边和最后一个item的右边仍有虚线轴。最开始想到通过添加空item来实现,但实际需求是在滑至第一个和最后一个是不允许继续滑动的,所以不能直接添加空item。
那怎么办呢?我们都知道轮播都是由视窗加container组成,通过计算定位container的位置来轮播。我不能在container里面直接添加DOM元素,否则会影响轮播组件的计算。但是我们可以在container前后添加伪元素,这样就不会妨碍轮播定位的计算了。
代码语言:javascript复制.time-chart-item:nth-of-type(1):after { content: ''; width: pxToRem(116); height: pxToRem(144); display: block; position: absolute; background: url(./img/dashline.png) pxToRem(29) 0 no-repeat, url(./img/dashline.png) pxToRem(29 58) 0 no-repeat; top: pxToRem(28); left: pxToRem(-116);}
这里有个平时很少用到的background的都多背景方法,由于左右两边最多有两根虚线展示,backgound设置两个虚线图片即可。
本次上课-如何用CSS mask实现状态条
当看到视觉稿 学生在线时间状态条的时候,一眼看去ok完全没有难度,不就一个简单的状态条吗,只不过不连续罢了。写个div,overflow-hidden,只需计算绿色块的width值和left值即可,撸起袖子就是干,十分钟搞定。
可是设计走查时,却逃不过视觉设计同学的火眼金睛:“这里的绿色应该是覆盖灰色边框上面的!” 接下来为了满足视觉同学的要求可花费了不少功夫。因为在线状态条及其相关计算已经写好,最开始没有使用图表组件,因为我觉得这很简单,不需要杀鸡用牛刀,直接CSS可以实现。
写来改写代码,为了让绿色在线条覆盖背景border,我将绿色状态条覆盖在上层,但这又出现另外一个问题。绿色条块左右两侧由于不被父级overflowhiden遮住,在值未达到极值时,无法做到圆角转直线的效果。
传统的办法
在外面再套一层div,position设置为relative,设置圆角和overflow hidden,绿色块相对于这一层div定位,如果溢出就会被裁剪。
css遮罩
css 有一个 -webkit-mask 属性。它所提供类似于遮罩的能力,让原本CSS无法实现的shape通过图片也能做到。看了下面这个图就清楚了。
那么怎么应用-webkit-mask来实现不连续的状态条呢?其实只需要一个非透明的极小的png图,计算好宽度以及位置,再进行样式设置即可。
这里的-webkit-mask和所有background的多背景图使用是一样的,需要注意的是,这里的第一个参数值不要把它误会成是的x值,而是图片的x%与容器x%的重合点,这里很容易出错。以下是计算的代码和生成的css样式:
代码语言:javascript复制const maskArray = [];InClassState.map((item) => { const { start_inclass: start, end_inclass: end } = item; const left = (start - lessonBeginTime) / allTime; const width = (end - start) / allTime; const maskLeft = left / (1 - width); maskArray.push(`url(${maskimg}) no-repeat ${maskLeft.toFixed(2) * 100}% 0/ ${width * 100}% 100%`);});style.WebkitMask = maskArray.join(',');
代码语言:javascript复制-webkit-mask:url(//fudao.qq.com/block_0bb81cb….png) 0% 0px / 60% 100% no-repeat,url(//fudao.qq.com/block_0bb81cb….png) 76% 0px / 15% 100% no-repeat,url(//fudao.qq.com/block_0bb81cb….png) 106% 0px / 15% 100% no-repeat;
只需要通过一个元素。
关注我们
IMWeb 团队隶属腾讯公司,是国内最专业的前端团队之一。
我们专注前端领域多年,负责过 QQ 资料、QQ 注册、QQ 群等亿级业务。目前聚焦于在线教育领域,精心打磨 腾讯课堂、企鹅辅导 及 ABCMouse 三大产品。
社区官网:
http://imweb.io/
加入我们:
https://hr.tencent.com/position_detail.php?id=45616
扫码关注 IMWeb前端社区 公众号,获取最新前端好文
微博、掘金、Github、知乎可搜索 IMWeb 或 IMWeb团队 关注我们。