只用一个 <img> 标签,没有额外 div,没有伪元素,连个 span 都不给加,就想给图片整出花里胡哨的边框和装饰?听着有点离谱,但 CSS 这玩意儿的潜力比想象中大得多。接下来就掰扯掰扯,怎么靠渐变、描边、内边距这几板斧,把单张图片玩出各种骚操作。
基础装饰三板斧
核心思路其实贼简单:在图片周围硬挤出一圈空白区域,然后往那空白里填背景、画渐变、套轮廓。由于图片本身会盖住背景,得靠 padding 和透明 border 撑出空间,让背景能露脸。
img {
--s: 10px; /* 控制装饰尺寸 */
padding: var(--s);
border: calc(2 * var(--s)) solid #0000;
outline: 1px solid #000;
outline-offset: calc(-1 * var(--s));
background: conic-gradient(from 90deg at 1px 1px, #0000 25%, #000 0);
}上面这段代码里,--s 变量决定了装饰条的粗细。padding 加上透明 border 总共腾出 3 * var(--s) 的空白区。为什么要同时用 padding 和 border?因为 background-origin 默认是 padding-box,而 background-clip 默认是 border-box——这俩货搭配起来,能让渐变既填满内边距区域,又偷偷延伸到边框下面。把边框设成透明色,底下那层渐变重复纹路就透出来了,形成双线框的既视感。outline 配合负偏移量 calc(-1 * var(--s)),刚好在渐变顶部压出一个纯色方块,整个装饰瞬间不单调。
这种玩法有个坑:outline 不支持圆角,如果图片带了 border-radius,外轮廓还是方愣愣的,会穿帮。解决方法?换用 box-shadow 或者多叠几层渐变,不过代码复杂度就上去了。
下面这张表把关键属性的作用捋清楚了:
| 属性 | 作用 |
|---|---|
| padding | 撑开内边距 |
| border透明 | 扩展背景区域 |
| outline | 压出方形色块 |
| conic渐变 | 画角落纹路 |
角框架花式玩法
只做一圈普通边框太没劲,试试只占四个角的框架,鼠标滑过时再展开成全包围。这招需要每个角单独上一个 conic-gradient,总共四个渐变,分别定位到 0 0、100% 0、0 100%、100% 100%。
img {
--b: 5px; /* 边框厚度 */
--c: #0000 90deg, darkblue 0;
padding: 10px;
background:
conic-gradient(from 90deg at top var(--b) left var(--b), var(--c)) 0 0,
conic-gradient(from 180deg at top var(--b) right var(--b), var(--c)) 100% 0,
conic-gradient(from 0deg at bottom var(--b) left var(--b), var(--c)) 0 100%,
conic-gradient(from -90deg at bottom var(--b) right var(--b), var(--c)) 100% 100%;
background-size: 50px 50px;
background-repeat: no-repeat;
}
img:hover {
background-size: 51% 51%;
}每个渐变的尺寸固定为 50px 50px,只显示一个角的小方块。硬停点的写法 #0000 90deg, darkblue 0 里的 0 是个偷懒小技巧——浏览器会自动把它提升到前面最近的有效位置(也就是 90deg),省得写两遍 90deg。悬停时把背景尺寸从固定像素改成百分比 51%,四个角同时膨胀,中间稍微重叠 1% 防止留白缝隙。
操作中要注意:background-position 如果改成百分比值,计算方式会基于容器尺寸减去背景图尺寸,容易跑偏。所以四个角分别写死 0 0、100% 0 这种绝对位置最稳当。
要是觉得四个渐变太啰嗦,可以压缩成两个渐变再加动画。下面这个例子只用了两组 conic-gradient,配合 background-position 的过渡,让角框架从对角线两端钻出来。
img {
--b: 8px;
--s: 60px;
--g: 14px;
--c: #EDC951;
padding: calc(var(--b) + var(--g));
background-image:
conic-gradient(from 90deg at top var(--b) left var(--b), #0000 25%, var(--c) 0),
conic-gradient(from -90deg at bottom var(--b) right var(--b), #0000 25%, var(--c) 0);
background-position:
var(--_p, 0%) var(--_p, 0%),
calc(100% - var(--_p, 0%)) calc(100% - var(--_p, 0%));
background-size: var(--s) var(--s);
background-repeat: no-repeat;
transition:
background-position .3s var(--_i,.3s),
background-size .3s calc(.3s - var(--_i, .3s));
}
img:hover {
background-size: calc(100% - var(--g)) calc(100% - var(--g));
--_p: calc(var(--g) / 2);
--_i: 0s;
}--_p 和 --_i 这种带下划线的变量是内部优化用的,跟外部控制的 --b、--c、--s 区分开。悬停时 background-size 从固定 60px 变成 calc(100% - 14px),几乎撑满整个区域,同时 background-position 从角落往中间挪 7px,动画节奏用 --_i 错开时间,看着就像两把尺子同时画框。搞这套的时候得确保 padding 预留空间足够,否则渐变长大后会盖住图片边缘。
框架旋转脑洞
不画新框,直接让现有边框“扭”起来。原理是用四个方向的重复渐变,各自做位置动画,在角落交汇时产生折线错觉。
img {
--g: 4px;
--b: 12px;
--c: #669706;
padding: calc(var(--g) + var(--b));
--_c: #0000 0 25%, var(--c) 0 50%;
--_g1: repeating-linear-gradient(90deg ,var(--_c)) repeat-x;
--_g2: repeating-linear-gradient(180deg,var(--_c)) repeat-y;
background:
var(--_g1) var(--_p, 25%) 0,
var(--_g2) 0 var(--_p, 125%),
var(--_g1) var(--_p, 125%) 100%,
var(--_g2) 100% var(--_p, 25%);
background-size: 200% var(--b), var(--b) 200%;
transition: .3s;
}
img:hover {
--_p: 75%;
}repeating-linear-gradient 配合 #0000 0 25%, var(--c) 0 50% 制造出两段透明加一段彩色的重复图案。注意 0 25% 这种写法:第一个 0 是起始位置,第二个 25% 是结束位置,中间没有颜色过渡,直接硬切。每个渐变只负责一条边,通过改变 --_p 从 25% 到 75%,让上下左右四个边框同步滑动,交汇处自然形成折角。需要留意 background-size 设成 200% 而不是 100%,因为 repeating-gradient 的重复逻辑要求尺寸足够大才能覆盖整个区域,否则动画到一半会露馅。
要是想换口味,把 repeating-linear-gradient 换成 radial-gradient 还能做出圆形波纹扩散的悬停效果,代码结构类似,只是把渐变类型和 background-size 的数值调整一下。拿捏住“单标签+渐变+位置动画”这个套路,基本能应付九成以上的图片装饰需求。
