// webpack.config.jsconst path = require('path')const HtmlPlugin = require('html-webpack-plugin')module.exports = {mode: 'development',entry: {entry1: './src/index.js'},output: {path: path.resolve(__dirname, 'dist'),filename: '[name].js'},module: {rules: [{test: /.css$/,use: ['style-loader', 'css-loader', 'postcss-loader']}]},plugins: [new HtmlPlugin({template: './src/index.html'})],devServer: {contentBase: path.resolve(__dirname, 'dist'),host: '0.0.0.0',port: 3005,compress: true,disableHostCheck: true}}
SVG
分别直接用xml的svg标签和css实现了两个100px,边框宽为1的矩形。
高清屏下效果如下。
1598073606858
width="100"height="100"fill="transparent"stroke-width="1"stroke="black"/>
stroke-width 和 border 一样,都将矩形的边设为了1px,但是用svg实现的矩形边框看起来却更细。 关键的地方是使用svg标记的 视口大小和使用rect标记的 矩形大小是 相同的。
svg中没有盒模型的概念,它的 stroke画线并 不是对应css中的border。 更像是不占空间的outline。因为不占空间,它会以rect(矩形)的边界为 中心画线,一条线一半宽度在矩形内,一半在矩形外。
而因为视口宽度正好等于矩形的大小,看到的线宽就只有一半了。
(用 svg画一个100px大小+1px边宽的方形)
(用css画一个100px大小+1px边框的方形 border-box)
如果把矩形缩小一点,不占满视口,这时候看到的border是完整的,所以和没处理过的1px一样粗。
border-image
border-image是三个属性的缩写
border-image-source: url('https://misc.aotu.io/leeenx/border-image/box.png');border-image-slice: 33% 20% 3 fill;border-image-repeat: stretch;
border-image-source:图片链接或base64;
border-image-slice:图片切割的四个位置。把图片切成9块,除中间一块,其他八块分别被当成边框使用。接受1-4个参数(使用类似于padding/margin的尺寸设置)。可以是百分比(相对于图片自身),也可以是数字(单位是px)。最后的fill决定中间那块图片会不会被当成background使用。
border-image-repeat:stretch/round(平铺)/repeat(重复)上下左右四个正位的图片怎样被当成border使用。
round(平铺)会压缩,repeat(重复)会剪裁。
border-image必须配合border使用。最终 border宽度是border-width。 border-style也必须指定,border-color可以不用。
伪类元素方案
完整代码
// index.html
// index.css.retina-border {position: relative;}.retina-border::before {content: '';position: absolute;width: 100%;height: 100%;transform-origin: left top;box-sizing: border-box;pointer-events: none;border-width: 1px;border-style: solid;border-color: #333;}@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) { .retina-border::before {width: 200%;height: 200%;transform: scale(0.5);}}@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) { .retina-border::before {width: 300%;height: 300%;transform: scale(0.33);}}.normal-border {border: 1px solid #333;}
具体实现
以两倍屏为例
.retina-border {position: relative;}.retina-border::before {content: '';position: absolute;top: 0px;right: 0px;width: 200%;height: 200%;transform: scale(0.5);transform-origin: left top;box-sizing: border-box;pointer-events: none;border-width: 1px;border-style: solid;border-color: #333;}
通过一个伪类选择器在retinaborder元素中加了一个子元素
border-width: 1px 将边框的宽度设为1px。
width:200% 然后将 伪类元素的宽高都设置成父元素的2倍。( 但是边框还是1px)
transform:scale(0.5) 将 伪类元素的x,y轴方向都缩放到0.5倍。
通过两次尺寸的设置,使这个伪类子元素保持内容的大小还是和父元素一样,但是 border:0.5px 的效果。
pointer-events: none 当有元素的层级重叠时,鼠标点击是无法穿透的。即绝对定位的伪类元素的层级更高,它底下的元素(即文字:retina border)无法被事件触发。置为none时, 绝对定位的元素不触发事件,底下的那层才能被选中。
其他css样式作用
伪类元素默认的 display:inline 。而 position:absolute 会使元素 display:block 。只有块级元素的尺寸(宽/高)设置才是有效的。
其中伪类选择器中 content 是必填项,不然无法生效
transform-origin的缩放的中心点,默认是元素中心,
transform-origin的缩放的中心点,默认是元素中心,和绝对定位的top,right一样,相对的是padding+content部分整个空间的位置
绝对定位的元素其top和right值是相对于padding+content的,默认值是从content开始,所以要规定都是0,否则当父元素有padding时,border就移位了
(如果删去position:absolute)
(如果删去position:absolute+display:block)
当使用百分比时,其父元素的高度必须显式指定,(20px/20view)不能是由子元素撑开的,但是宽度是可以的。两种方案比较 兼容性
svg方案经过postcss处理,最终会影响浏览器兼容性的是 border-image属性
伪类元素元素:方案最终影响兼容性的是 transform属性
1598076296220
结论:svg方案的兼容性更好。
灵活性
由于svg只能画出特定的形状,所以无法实现圆角边框。而伪类元素方案可以。
学习成本
svg方案所用到的border-image属性、svg特性的理解成本较高,并且需要postcss-write-svg处理。伪类元素方案相较简单。
总结
通常情况,伪类元素方案更好,无论是从成本还是灵活性出发。如果是为了更高的兼容性选择svg方案,border-image属性一定要使用缩写。(不然兼容性会更差兼容性测试)