如何编写通用的 Helper Class
前言
什么是 helper ?任何框架都不是万能的,而业务需求却是多种多样,很多时候我们只需要更改组件的部分属性,而 helper 就是调整细节的工具。我在之前的文章《如何编写轻量级 CSS 框架》中也举过例子,我们完全没必要因为几个属性的不同而重新编写新组件。大部分的 helper 都是一个类对应一个 CSS 属性,属于最细小的类。通过工作的实践总结,我觉得编写一套简单易用、通俗易懂的 helper 非常重要。本文的目的就是探讨 helper 的组成部分、编写方式以及如何精简 helper 的命名。
组件与零件
详细介绍如何编写 helper 之前,先说一下我对于组件以及零件的看法。在之前编写轻量级 CSS 框架的时候,我们是以组件的方式开发。而编写 helper 更像是开发一个零件,因为 helper 的属性单一,而且多个 helper 可以形成一个组件。比如下面的例子:
假设有 .boxes
组件
.boxes {
border: 1px solid #eee;
border-radius: 5px;
margin-bottom: 15px;
overflow: hidden;
}
假设有如下 helper
.b-1 {
border: 1px solid #eee !important;
}
.r-5{
border-radius: 5px !important;
}
.m-b-15{
margin-bottom: 15px !important;
}
.overflow-hidden {
overflow: hidden !important;
}
则 .boxes
= .b-1
+ .r-5
+ .m-b-15
+ .overflow-hidden
我是一个模型爱好者,这样的组合方式让我想到了寿屋的 HEXA GEAR 系列模型,这个系列的特点是“零件+零件=组件、组件+组件=骨架、骨架+骨架=素体、素体+武装=机体”。
在编写 helper 的时候,基于以上想法,我在思考是否可以把 helper 拆分的足够精细,这样它就可以自成一体形成一个框架,也就是“零件+零件=组件、组件+组件=框架”。令人遗憾的是,我的想法已经被人实践,前几天浏览 GitHub 时发现了相关的项目 tailwindcss,这个框架就是以 helper 为基础,通过属性叠加的方式添加样式。
组件式框架和零件式框架是两种完全不同的思想,难分伯仲,各有优缺点。
Helper 的组成部分
一套完整的 helper 应该包含哪些内容呢?一般常用的有 padding
、margin
、font-size
、font-weight
等。为了编写更为通用的 helper,我们需要更细致的划分。虽然我们并没有打算把它写成一个框架,但是我们希望 helper 的功能足够强大。通过对比和思考,我将 helper 暂时划分成以下几个模块:
- Colors(颜色,包括 bg-color 及 text-color)
- Paddings(内边距序列)
- Margins(外边距序列)
- Typography(排版,包括 font-size 及 font-weight)
- Border(边框线)
- Radius(圆角)
- Shadow(阴影)
- Size(尺寸,包括 height 及 width)
- Gutters(栅格间距序列)
- Alignment(主要是 vertical-align)
- ...
和之前编写轻量级框架一样,我们同样使用 Sass 预编译器。helper 类几乎都是 Sass 循环生成的,所以源代码看上去很精简。
颜色变量
因为颜色稍微特殊一点,我将颜色与其它内容分开单独介绍。在编写轻量级框架的时候,我也定义了常用的一些颜色,但是面对特殊需求时略显单一,所以我们需要使用 helper 扩充颜色集群。但是颜色是一个无法量化的概念,所以再强大的 helper 也无法面面俱到,只能是一定程度上的补充。参考常用的颜色值,最终我设置了红、橙、黄、绿、青、蓝、靛、紫、粉、冷灰、暖灰等几种色系。
其中每个颜色都有六个亮度值,分别用 -lightest
、-lighter
、-light
、-dark
、-darker
、-darkest
表示,此处有参考 tailwindcss 的颜色命名。这些颜色都是通过 Sass 的颜色函数生成的。以灰色为例,Sass 代码如下:
$gray:#999;
$gray-light:lighten($gray, 15%);
$gray-lighter:lighten($gray, 25%);
$gray-lightest:lighten($gray, 35%);
$gray-dark:darken($gray, 15%);
$gray-darker:darken($gray, 25%);
$gray-darkest:darken($gray, 35%);
这些颜色序列看上去很像一套马克笔,不过马克笔灰色系更丰富,包括冷灰、暖灰、蓝灰、绿灰。
其中背景色的循环方式如下,为了便于循环,我们定义了一个 color map
,然后用 @each
方法循环。
$color-list:(
'gray':$gray,
'brown':$brown,
'red':$red,
'orange':$orange,
'yellow':$yellow,
'green':$green,
'teal':$teal,
'blue':$blue,
'indigo':$indigo,
'purple':$purple,
'pink':$pink
); @each $name,$color in $color-list {
.bg-#{$name} {
background-color: $color;
}
.bg-#{$name}-light {
background-color: lighten($color, 15%);
}
.bg-#{$name}-lighter {
background-color: lighten($color, 25%);
}
.bg-#{$name}-lightest {
background-color: lighten($color, 35%);
}
.bg-#{$name}-dark {
background-color: darken($color, 15%);
}
.bg-#{$name}-darker {
background-color: darken($color, 25%);
}
.bg-#{$name}-darkest {
background-color: darken($color, 35%);
}
}
命名策略
理所当然,我又提到了命名策略。在编写轻量级框架的时候,我也着重讨论了类命名策略以及比较了一些框架的命名方式。无论是框架还是 helper,类命名都决定了其易用性,而且会影响使用者的习惯,所以我会从简洁、直观、易用等几个角度命名。不过 helper 的命名比较简单,因为几乎大多数都是单一的 CSS 样式,所以命名策略基本都是对 CSS 属性的抽象与简化。
数字型命名 VS. 尺寸型命名
我在工作中接触过两种 helper 序列的表示方法,一种是常见的数字型,另一种是尺寸型。以 padding
为例:
数字型
.p-5 {
padding: 5px !important;
}
.p-10 {
padding: 10px !important;
}
.p-15 {
padding: 15px !important;
}
.p-20 {
padding: 20px !important;
}
.p-25 {
padding: 25px !important;
}
尺寸型
.p-xs {
padding: 5px !important;
}
.p-sm {
padding: 10px !important;
}
.p-md {
padding: 15px !important;
}
.p-lg {
padding: 20px !important;
}
.p-xl {
padding: 25px !important;
}
虽然在实际应用时,尺寸型写法并没有什么不妥,但很明显它的扩展性很差,而且不直观。作为例子,我只写了五个数值,但如果我们希望添加更多的 padding 值的话,尺寸型命名就乏力了。我认为,凡是可以量化的属性,比如 padding
、margin
、font-size
、border-width
等,应该直接用数值表示,而对于不可以量化的属性,比如 box-shadow
,用尺寸型命名比较合适。
精简命名
大多数的 helpr 命名都是 CSS 属性的首字母缩写形式。比如 p
表示 padding
、m
表示 margin
、f-s
表示 font-size
等。这符合我们期望的简洁直观的要求。但也不能唯缩写论,所有的命名都用缩写,因为有些属性的缩写会重复,而且有些缩写之后就不知道具体含义了。我们可以沿用之前的规则,可以量化的属性都用缩写,不可以量化的属性用简化的全称(比如 box-shadow
可以替换为 shadow
)。
以 padding 循环为例:
@for $counter from 0 through 6 {
.p-#{ $counter * 5 } {
padding: ($counter * 5px) !important;
}
.p-t-#{ $counter * 5 } {
padding-top: ($counter * 5px) !important;
}
.p-r-#{ $counter * 5 } {
padding-right: ($counter * 5px) !important;
}
.p-b-#{ $counter * 5 } {
padding-bottom: ($counter * 5px) !important;
}
.p-l-#{ $counter * 5 } {
padding-left: ($counter * 5px) !important;
}
}
对于其它几个 helper 与此类似,循环也很简单。
关于 Margin 负值
margin 的 helper 相比其它来说比较特殊,因为它有负值,所以我们必须考虑如何表示负值。有些框架用 n
(negtive)表示负值。比如 m-{t,r,b,l}-n-*
的形式:
.m-t-n-5 {
margin-top: -5px !important;
}
.m-r-n-5 {
margin-right: -5px !important;
}
.m-b-n-5 {
margin-bottom: -5px !important;
}
.m-l-n-5 {
margin-left: -5px !important;
}
我觉得完全可以简化一步,用 -
表示负值,简单易懂,如下:
.m-t--5 {
margin-top: -5px !important;
}
.m-r--5 {
margin-right: -5px !important;
}
.m-b--5 {
margin-bottom: -5px !important;
}
.m-l--5 {
margin-left: -5px !important;
}
虽然这种命名方式很简洁,但看上去和其它 helper 不太统一。
关于圆角
圆角的 CSS 属性名为 border-radius
,如果直接简写的话和 border-right
就重复了,参见其它框架的表示方法有 corner-rounded
、rounded
等。我们也可以简化一下,比如直接用 r
表示,既可以代表 rounded
也可以代表 radius
,一举两得。这样的表示方法应该不会有歧义,毕竟在我们的脑海中,r
表示半径算是一个根深蒂固的概念。Sass 代码如下:
@for $counter from 0 through 10 {
.r-#{ $counter } {
border-radius: ($counter * 1px) !important;
}
.r-t-l-#{ $counter } {
border-top-left-radius: ($counter * 1px) !important;
}
.r-t-r-#{ $counter } {
border-top-right-radius: ($counter * 1px) !important;
}
.r-b-r-#{ $counter } {
border-bottom-right-radius: ($counter * 1px) !important;
}
.r-b-l-#{ $counter } {
border-bottom-left-radius: ($counter * 1px) !important;
}
}
我们用 -full
表示 100%
,其它框架也基本如此,稍后再谈论 r-100%
这种形式的可行性及问题所在。
.r-full {
border-radius: 100%
}
.r-t-l-full {
border-top-left-radius: 100%
}
.r-t-r-full {
border-top-right-radius: 100%
}
.r-b-r-full {
border-bottom-right-radius: 100%
}
.r-b-l-full {
border-bottom-left-radius: 100%
}
同样的,高度和宽度的 100%
数值也用 -full
表示,循环方式类似。
关于阴影
我们在之前反复提到了阴影属于非量化的属性,所以只能使用尺寸型命名法,当然用数字也不是不可以,一会儿再详细说明。先看源代码:
.shadow-xs{
box-shadow:0 1px 5px 1px rgba(0,0,0,.15);
}
.shadow-sm{
box-shadow:0 2px 10px 2px rgba(0,0,0,.15);
}
.shadow-md{
box-shadow:0 3px 20px 3px rgba(0,0,0,.15);
}
.shadow-lg{
box-shadow:0 4px 30px 4px rgba(0,0,0,.15);
}
.shadow-xl{
box-shadow:0 5px 40px 5px rgba(0,0,0,.15);
}
整体而言,比较简洁,不过阴影的数值我是粗略添加的,实际情况要做调整。说点题外话,我个人觉得对于非量化的属性本身而言,或许用处就不大,因为这些属性能够满足业务需求的可能微乎其微,但是它仍然是不可缺少的一部分。所以说“通用的” helper 并不一定通用。
关于强度表示法
通过 font-weight
说一下关于强度的表示法,font-weight
的 CSS 属性本身就有两种表示法,一种是直接文字命名,比如 .f-s-thin
, .f-s-normal
, .f-s-bold
等,另一种是比较直接的 100 ~ 900 数值型表示法。以我个人观点,我更倾向于数值型表示法,简单直观,并没有歧义,也算是约定俗成的规定吧。font-weight
的循环比较简单,而且数值有限,我们可以直接写出从 100 ~ 900 的所有 helper。其它类似的 helper 也可以用 100 ~ 900 表示强度,比如颜色。
需要注意的是,编写 helper 时一定要对数值型、尺寸型、强度型命名做好归类与统一,切记毫无章法地胡乱使用。
类命名中的特殊字符
对于 r-100%
或者 w-100%
这样的写法是可以的,但是在定义 CSS 时要进行字符转义,比如
.r-100\% {
border-radius: 100%
}
使用方式如下
<div class="r-100%"></div>
但是这种写法总给人怪怪的感觉,而且输入时要按 shift
+ %
,不太方便,所以暂时只作为参考。
另外需要说明一点,我们可以通过特殊字符定义百分数,比如:
.w-50 {
width: 50px;
}
.w\:50 {
width: 50%
}
通过约定的这种规则,我们就可以为 helper 添加栅格系统了。不过这只是暂时的想法,毕竟我们已经有一套轻量级 CSS 框架了。
序列数量
因为 helper 是循环生成的,所以循环的数量决定了 helper 的丰富度。那么循环的数量多少合适呢?这是所有 helper 最难统一的地方。不可否认,helper 的数量越多,通用性越强,也就越灵活。任何事物都有两面性,虽然 helper 越多越好,但是数量太多会造成文件臃肿。目前我写的 helper 的文件体积几乎和之前的轻量级框架差不多,某种程度上来说确实在向“零件化”的框架发展。另一方面,其实 helper 并没有必要写的太全面,很多数值存在冗余。
简单来说,对于有限值的 helper 就可以全部写出,比如对其方式、font-weight 等。而对于任意数值的 helper 来说,我们需要选择常用的一些数值,比如 padding、margin 等属性,基本 1~50 px 之间就可以了,而圆角 1~20 px 足矣。不能量化的属性比如阴影就完全看个人喜好了,我觉得五个尺寸就差不多。对于实在特殊的需求也只能特殊对待了。
演示
现在我们测试一下我们所写的 helper 是不是能够满足一般需求,比如一个带有圆角阴影的用户卡片,如下:
See the Pen snack-helper-test by Zongbin (@nzbin) on CodePen.
这个实例全部是用 helper 完成的,可惜这套 helper 没有栅格系统,所以布局并不灵活,但是结合之前的轻量级框架,会显示出它强大的功能。
总结
编写 helper 比编写框架要容易的多,但简单易用、通俗易懂的 helper 还需要严谨的思考,详细的 helper 可以参见 GitHub 源码。虽然我一直声称没有打算把 helper 写成一个框架,但随着细节的追加与调整,比如添加栅格系统,这个通用的 helper 已经趋向于一个“零件化”的框架了。至于组件式框架和零件式框架哪个更好,这是一个很难选择的问题。但是我更倾向于组件与零件的结合,因为我不希望整个 HTML 文件被冗长的 CSS 类装饰的支离破碎。
如何编写通用的 Helper Class的更多相关文章
- <五>JDBC_利用反射及JDBC元数据编写通用的查询方法
此类针对javaBean类写了一个通用的查询方法,List<javaBean> 通用查询更新中...:通过学习,深刻体会到学会反射就等于掌握了java基础的半壁江山! 一.使用JDBC驱动 ...
- JDBC学习笔记(5)——利用反射及JDBC元数据编写通用的查询方法
JDBC元数据 1)DatabaseMetaData /** * 了解即可:DatabaseMetaData是描述数据库的元数据对象 * 可以由Connection得到 */ 具体的应用代码: @Te ...
- 【转】JDBC学习笔记(5)——利用反射及JDBC元数据编写通用的查询方法
转自:http://www.cnblogs.com/ysw-go/ JDBC元数据 1)DatabaseMetaData /** * 了解即可:DatabaseMetaData是描述数据库的元数据对象 ...
- Java -- JDBC_利用反射及 JDBC 元数据编写通用的查询方法
先利用 SQL 进行查询,得到结果集: 利用反射创建实体类的对象:创建对象: 获取结果集的列的别名: 再获取结果集的每一列的值, 结合 3 得到一个 Map,键:列的别名,值:列的值: 再利用反射为 ...
- sql server编写通用脚本自动统计各表数据量心得
工作过程中,如果一个数据库的表比较多,手工编写统计脚本就会比较繁琐,于是摸索出自动生成各表统计数据量脚本的通用方法,直接上代码: /* 脚本来源:https://www.cnblogs.com/zha ...
- JDBC课程5--利用反射及JDBC元数据(ResultSetMetaData)编写通用的查询方法
/**-利用反射及JDBC元数据编写通用的查询方法 * 1.先利用SQl语句进行查询,得到结果集--> * 2.查找到结果集的别名:id--> * 3.利用反射创建实体类的对象,创建aut ...
- 缓冲区溢出分析第05课:编写通用的ShellCode
前言 我们这次的实验所要研究的是如何编写通用的ShellCode.可能大家会有疑惑,我们上次所编写的ShellCode已经能够很好地完成任务,哪里不通用了呢?其实这就是因为我们上次所编写的ShellC ...
- 泛型理解及应用(二):使用泛型编写通用型Dao层
相信目前所有的IT公司网站在设计WEB项目的时候都含有持久层,同样地使用过Hibernate的程序员都应该看过或者了解过Hibernate根据数据库反向生成持久层代码的模板.对于Hibernate生成 ...
- 在html中使用thymeleaf编写通用模块
在编写页面时,常常会需要用到通用模块,比如header部分.footer部分等. 项目前端使用的是themeleaf模板引擎,下面简单介绍下使用themeleaf写header通用模块: 1. 通用部 ...
随机推荐
- C# XML序列化
/// <summary> /// XML序列化为指定对象 /// Author:taiyonghai /// Time:2016-08-22 /// </summary> / ...
- ConcurrentHashMap源码及分析
ConcurrentHashMap是在jdk1.5版本开始,存在于java.util.concurrent包下.本文主要是针对jdk1.7版本. 由于HashMap是非线程安全的,HashTable虽 ...
- 通过nginx的fastcgi_param来设置环境变量
在nginx配置文件中,可以在nginx总体的配置文件nginx.conf中,也可以在单独的网站配置环境中进行设置,如:www.tomener.com.conf 在配置环境server段locatio ...
- win10 uwp 使用油墨输入
win10可以很简单在我们的app使用自然输入,这篇文章主要翻译https://blogs.windows.com/buildingapps/2015/09/08/going-beyond-keybo ...
- RT-thread 利用Scons 工具编译提示python编码错误解决办法
错误信息: scons: Reading SConscript files ...UnicodeDecodeError: 'ascii' codec can't decode byte 0xbd in ...
- 自学 Python 3 最好的 入门 书籍 推荐(附 免费 在线阅读 下载链接)
请大家根据自己的实际情况对号入座,挑选适合自己的 Python 入门书籍: 完全没有任何编程基础:01 号书 少量编程基础,不求全,只希望能以最快的速度入门:02 号书 少量编程基础,有一定的英文阅读 ...
- Python 3.6.3 官网 下载 安装 测试 入门教程 (windows)
1. 官网下载 Python 3.6.3 访问 Python 官网 https://www.python.org/ 点击 Downloads => Python 3.6.3 下载 Python ...
- php 守护进程类
最近个人项目中需要后台运行任务,之前一直是用nouhp & + 重定向输出 来后台跑任务,后来觉得不好维护原始数据,同时可能也没有直接操作进程那么稳吧(没验证).废话少说,来看分析. 首先,我 ...
- Zookeeper 笔记-角色
leader:提供读写服务,负责投票的发起和决议,更新系统状态 follower:为客户提供读服务,如果写服务则转发给leader,选举过程参与投票 observer:为客户端提供读服务,如果是写服务 ...
- [extjs(1)]MyEclipse2014安装ext4插件Spket
1 解压好的Spket目录如下 2 建议以link方式安装Spket到MyEclipse中 找到MyEclipse的安装目录 如 3 在MyEclipse 的根目录新建一个目录extjs 当然也可 ...