《CSS Secrets》是@Lea Verou最新著作,这本书讲解了有关于CSS中一些小秘密。是一本CSSer值得一读的一本书,经过一段时间的阅读,我、@南北和@彦子一起将在W3cplus发布一系列相关的读后感,与大家一起分享。
尽管这不是一个常见的文本效果,有时候有一些比较短的文本需要遵循环形路径显示。这种时候,CSS就弃我们而去了。没有任何CSS属性或功能可以完成这个效果,我们唯一想到的CSS解决方案都非常麻烦,所以我们也就只是想想而已。真的没有什么办法可以实现这样的样式吗?除了使用图像,除了不影响我们文本的整体美观?
在juliancheal.co.uk上使用环形文本作为左边的按钮(知道我指的是哪里吗?);注意那里的环形文本是避免打破按钮的唯一办法,按钮形状的中间位置是由孔和螺纹组成的
解决方案
有一些脚本可以完成这个效果。它们通过将每个字母包裹在单独的<span>
元素中,然后旋转到合适的角度来把它们一个一个组合成圆。这个方案不仅非常麻烦,而且还给页面的DOM元素添加了很多不必要的臃肿的标记。
尽管目前没有办法通过纯CSS来完成这个效果,但是我们可以通过一点内联SVG来很简单地完成。SVG本身就支持文本以任何路径显示,环形只不过是路径的一个特殊情况。我们来试一下!
SVG中文本按路径显示的基本方案是通过一个<textPath>
元素来包裹我们的文本,把它放到一个<text>
元素中。<textPath>
元素通过id
引用我们的路径中定义的<path>
元素。在内联SVG中的文本还继承了我们的字体样式(除了line-height
,因为这是SVG中默认的样式),所以我们不需要担心这个问题,就像我们引入一个外部的SVG图像一样。
可惜,
<textPath>
只可以存在于<path>
元素中,这就是我们为什么不可以使用可读性更好的<circle>
元素作为我们的路径圆的原因。
假设我们要把“circular reasoning works because”这句话做成环形文本,占据一个圆圈的整个圆周,如图所示。
我们需要在HTML中添加一个内联SVG,并定义一个圆形路径:
<div class="circular">
<svg viewBox="0 0 100 100">
<path d="M 0,50 a 50,50 0 1,1 0,1 z" id="circle" />
</svg>
</div>
注意我们是通过viewBox
,而不是width
和height
来定义它的单位。这可以让我们设置坐标系统和长宽比,而不是一个固定的大小。这不仅是因为它更紧凑,还因为它帮我们节省了几行CSS,因为我们不再需要为<svg>
元素应用值为100%
的width
和height
值——它会自动调整为其容器的大小。
如果你不清楚path
的语法,别担心。大家都是一样的,甚至是那些已经了解了SVG path语法的人,还是会忘记语法。如果你好奇,下面这三个命令已经非常好地概括了下面这些语法:
M 0,50
: 移动到点 (0,50)a 50,50 0 1,1 0,1
: 从你当前点的位置,画一条圆弧,到一个新的点,新点的位置距原点往右0
,往下1
。圆弧的半径为50
,水平和垂直方向都是。除了两种可能的角度,选择两条可能的弧形里边最大的,选择两个点中在右边的,而不是左边那个。z
: 通过直线段闭合路径。
为什么SVG路径的语法这么隐蔽?它当初被设计出来的时候,大家觉得没有人会手动编写SVG,所以SVG工作小组尽可能地用了最紧凑的语法,来减少文件大小。
目前,我们的路径只是一个黑色的圆。
我们需要通过<text>
和<textPath>
元素添加文本,并通过xlink:href
属性把它链到我们的圆上,如下:
<div class="circular">
<svg viewBox="0 0 100 100">
<path d="M 0,50 a 50,50 0 1,1 0,1 z" id="circle" />
<text>
<textPath xlink:href="#circle">
circular reasoning works because
</textPath>
</text>
</svg>
</div>
如上图所示,尽管我们还需要做很多东西,来让它更像样更有可读性,我们已经取得了我们想要的效果,这是CSS很多年内都无法完成的!
下一步是移除我们的圆形路径上的黑色填充。我们并不希望圆形以任何方式显示出来;我们只是想要它作为我们文本的一个引导。有很多方式可以完成,比如把它加入到一个<defs>
中(这是专门为此而设计的)。但是,这里我们希望能够尽量较少SVG标签,所以我们直接通过CSS应用一个fill: none
。
.circular path { fill: none; }
现在黑色的圆已经消失了
我们可以仔细研究其它问题了。下一步最大的问题是,我们大多数的文本是在SVG元素之外的,还被裁剪了。为了解决这个问题,我们需要让我们的容器元素更小一些,然后为SVG元素应用overflow: visible
,这样它就不会裁剪它的视窗之外的任何内容了:
.circular {
width: 30em;
height: 30em;
}
.circular svg {
display: block;
overflow: visible;
}
你可以在上图中看到效果。注意到我们已经差不多完成了,但是有一些文本还是被裁剪了。原因是SVG元素是根据它的尺寸浮动的,而不是溢出。因此,事实上,溢出盒子边界的<svg>
元素不会让整个SVG元素往下浮动。我们需要手动添加一个外边距来完成:
.circular {
width: 30em;
height: 30em;
margin: 3em auto 0;
}
.circular svg {
display: block;
overflow: visible;
}
就是它了!我们的示例现在看起来和下图非常相像
文本也是可访问的。如果我们的圆形文本只有一个实例(如,一个网站的logo),那我们到这里就完成了。但是,如果我们关于这种类型的文本有不止一个实例,我们不希望每次都重复这些SVG标签,可以写一个简短的脚本来自动生成所需的SVG元素,如类似这样的标签:
<div class="circular">
circular reasoning works because
</div>
该代码可以通过一个“circular”类遍历所有元素,移除它们的文本并把它存储在一个变量中,并添加必要的SVG元素:
$$('.circular').forEach(function(el) {
var NS = "http://www.w3.org/2000/svg";
var xlinkNS = "http://www.w3.org/1999/xlink";
var svg = document.createElementNS(NS, "svg");
var circle = document.createElementNS(NS, "path");
var text = document.createElementNS(NS, "text");
var textPath = document.createElementNS(NS, "textPath");
svg.setAttribute("viewBox", "0 0 100 100");
circle.setAttribute("d", "M0,50 a50,50 0 1,1 0,1z");
circle.setAttribute("id", "circle");
textPath.textContent = el.textContent;
textPath.setAttributeNS(xlinkNS, "xlink:href", "#circle");
text.appendChild(textPath);
svg.appendChild(circle);
svg.appendChild(text);
el.textContent = '';
el.appendChild(svg);
});
扩展阅读
如需转载,烦请注明出处:http://www.w3cplus.com/css3/css-secrets/circular-text.html