今天来看一种十分常见的交互:提示框(tooltips)。一般提示框都是纯色的,好比下面这个css
这类布局实现还不算复杂,能够用一个圆角矩形和一个小三角拼接造成,设置相同的颜色就能够了html
这个并非本文的重点,有兴趣的能够访问 css-tips (codepen.io)web
有时候,为了突出强调产品的特色或者为了跟随设计的潮流,设计会用上渐变背景,好比 lulu UI Edge 版本中的 Tips 组件canvas
若是仍然采用“拼接”的方式,不可避免会出现衔接不上的问题,有明显的“割裂”感,视觉还原会大打折扣api
那么,如何实现这类效果呢?一块儿来看看吧svg
clip-path 多是不少人立刻就能想到的方式。可是实际操做下来,仍是会遇到不少麻烦wordpress
如何解决这个问题呢?其实把 2 和 3 结合起来就能够了函数
这里须要两个相同大小的容器,能够用 ::before 和 ::after 来代替,而后设置相同的背景色,能够经过自定义属性定义布局
.tips{ position: relative; --bg: linear-gradient(45deg, #ff3c41, #ff9800); } .tips::before,.tips::after{ content:''; position: absolute; width: 100%; height: 100%; left: 0; top: 0; background: var(--bg);/*彻底相同的背景*/ z-index: -1; }
为何要用两个相同大小的容器呢? 这是为了保证接下来渐变背景在裁剪时彻底吻合post
接着其中一个裁剪成圆角矩形,另一个裁剪成小三角,而后重叠起来就能够了
.tips::before{ clip-path: inset(0 0 5px 0 round 5px); /*round 能够设置圆角*/ } .tips::after{ clip-path: polygon(calc(50% - 5px) calc(100% - 5px), calc(50% + 5px) calc(100% - 5px), 50% 100%); /* 实现小三角,只须要3个点的坐标就能够了 */ }
能够看到提示框彻底是自适应的,实时效果以下
完整代码可访问 tooltips-clip-path (codepen.io)
除了 clip-path ,mask 也是一种思路。若是还不熟悉 mask,能够参考这一篇 奇妙的 CSS MASK (juejin.im) 。这里的原理以下
利用 mask ,如今的问题就转变成了:如何经过 CSS 绘制这样一个图形?
没有什么图形是 CSS 渐变 绘制不出来的,这个也不例外。首先咱们把这个图形进行分解,这里能够分红一个圆角矩形和一个三角形,三角形比较容易,能够经过 conic-gradient 或者 linear-gradient 绘制
圆角矩形就稍微有点麻烦了,不过仍是能够分解的,以下
能够由4个径向渐变和2个线性渐变合成,用代码实现就是
tips{ -webkit-mask-image: /*4个径向渐变和2个线性渐变*/ radial-gradient(circle at 5px 5px, green 5px,transparent 0), radial-gradient(circle at 5px 5px, green 5px,transparent 0), radial-gradient(circle at 5px 5px, green 5px,transparent 0), radial-gradient(circle at 5px 5px, green 5px,transparent 0), linear-gradient(red,red), linear-gradient(blue,blue); -webkit-mask-size: 10px 10px, 10px 10px, 10px 10px, 10px 10px, 100% calc(100% - 15px), calc(100% - 10px) calc(100% - 5px) -webkit-mask-position: left top, right top, left 0 bottom 5px, right 0 bottom 5px, left 5px, 5px top; -webkit-mask-repeat: no-repeat }
只要有点耐心,均可以很顺利的写出来
可是...
太长了,有不少重复的(4个radial-gradient),很是啰嗦,有没有什么办法优化呢?这里有一个技巧,碰到重复有规律的东西,能够多想一想 repeat,利用背景的平铺特性,合理设置背景尺寸就能够了,以下
能够看到,背景尺寸设置成 calc(100% - 10px) 就能够达到平铺效果,代码实现就是
tips{ -webkit-mask-image: /*只须要一个径向渐变便可*/ radial-gradient(circle at 5px 5px, green 5px,transparent 0), linear-gradient(red,red), linear-gradient(blue,blue); -webkit-mask-size: calc(100% - 10px) calc(100% - 15px),/*圆角的尺寸,高度因为还须要减去三角形尺寸,因此多了5px*/ 100% calc(100% - 15px), calc(100% - 10px) calc(100% - 5px); -webkit-mask-position: left top, left 5px, 5px top; -webkit-mask-repeat: repeat,no-repeat,no-repeat; }
是否是精简了许多?而后再把三角形的合过来就好了,能够获得以下效果
完整代码可访问 tooltips-mask-gradient (codepen.io)
尽管作了一些优化,上面的代码量仍然很是可观,有没有更加简便的方式呢?
想到了 svg...
通常状况下,svg 路径是固定尺寸的,只能 等比缩放 ,没法作到自适应。不过基本图形是支持自适应的,能够设置百分比尺寸,好比 <rect>
<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'> <rect rx="5" width='100%' height='100%'/> </svg>
rx
能够设置矩形的圆角,当不设置ry
时,默认与rx
相同
这样一个 svg 是能够自适应的,在改变尺寸的状况下不会变形(注意观察圆角),以下
三角形就很容易了,能够用 <polygon>
实现
<svg xmlns='http://www.w3.org/2000/svg'> <polygon points='0 0,10 0,5 5' /> </svg>
而后,把两段 svg 直接用做遮罩背景就好了,能够用 mask-size 和 mask-position 分别设置 尺寸 和 位置
tips{ -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><polygon points='0 0,10 0,5 5' /></svg>"),url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect rx='6' width='100%' height='100%'/></svg>"); -webkit-mask-size: 10px 5px, 100% calc(100% - 5px); -webkit-mask-repeat: no-repeat; -webkit-mask-position: center bottom, 0 0; }
svg 用做背景须要在前面添加
data:image/svg+xml
,而且内容须要转义,详细可参考这篇文章:
学习了,CSS中内联SVG图片有比Base64更好的形式
仍是挺不错的,代码量也很少,也比较容易理解,实时效果以下
完整代码可访问 tooltips-mask-svg (codepen.io)
再来介绍一种将来的解决方式, CSS paint 。关于 CSS paint,又称 “CSS 界的绘图板”,简单来讲,就是用 canvas 绘图的方式来绘制背景,canvas 几乎什么都能绘制吧,因此这是一种更为通用的解决方案。想快速了解 CSS paint 的能够参考这一篇入门文章:CSS届的绘图板CSS Paint API简介,不过目前仅支持 Chrome,兼容性以下
不过并不影响咱们的学习,毕竟是将来的解决方案,先看看大体的语法,以下
// paint-tips.js registerPaint('tips-bg', class { paint(ctx, size, properties) { // 在这里绘制背景,语法和canvas相似 } });
if (window.CSS) { CSS.paintWorklet.addModule('paint-tips.js'); }
tips{ -webkit-mask-image: paint(tips-bg); /*这里做为遮罩背景使用*/ }
下面就来绘制提示框了,若是仍然借助 mask ,那么问题就变成了:如何经过 canvas 绘制这样一个图形?
在 canvas 中,相对于 CSS 来讲, 这类图形简直就是小儿科,只须要使用 lineTo 和 arc 两个指令就能够绘制了。最关键的一点是,这里的尺寸是实时渲染的,能够经过 size 来获取
关于 canvas 学习,这里推荐一下 张鑫旭老师的 Canvas API中文文档,api 和 案例比 mdn 文档清晰太多
绘制代码以下(下面就是很普通的 canvas 代码了,其实就是几段线段链接起来,而后填充纯色)
registerPaint('tips-bg', class { paint(ctx, size) { // ctx为绘制上下文,size为容器尺寸 const { width,height } = size; // 容器尺寸 const radius = 5; // 圆角大小 const deg = Math.PI / 2; const edge = 5; // 三角形大小 const pos = width / 2; // 三角形位置 ctx.beginPath(); ctx.moveTo(radius,0); ctx.lineTo(width-2*radius,0); ctx.arc(width-radius,radius,radius,-deg,0); ctx.lineTo(width,height-2*radius-edge); ctx.arc(width-radius,height-radius-edge,radius,0,deg); ctx.lineTo(pos+edge,height-edge); ctx.lineTo(pos,height); ctx.lineTo(pos-edge,height-edge); ctx.lineTo(radius,height-edge); ctx.arc(radius,height-radius-edge,radius,deg,2*deg); ctx.lineTo(0,radius-edge); ctx.arc(radius,radius,radius,-2*deg,-deg); ctx.closePath(); ctx.fillStyle = '#000'; ctx.fill(); } });
实时效果以下
完整代码可访问 tooltips-mask-paint (codepen.io)
另外,也能够经过 CSS 变量进行自定义,好比定义一个--r
为圆角大小,--t
为三角形大小
<tips style="--r:5;--t:5"></tips>
registerPaint('tips-bg', class { static get inputProperties() { // 定义容许的自定义属性 return [ '--r', '--t' ] } paint(ctx, size, properties) { // properties为自定义属性 const radius = Number(properties.get('--r')); const edge = Number(properties.get('--t')); // ... } })
能够看到绘制是实时更新的(改变圆角),无需 JS 额外处理,实时效果以下
完整代码可访问 tooltips-mask-paint-var (codepen.io)
有时候提示框可能还会有描边的效果,好比这样的
这类带描边的其实以上方式都不太适用,clip-path 和 mask 都没法实现描边,不过有一个边框生成方案能够参考:有意思!不规则边框的生成方案 (juejin.cn),惋惜效果不是特别完美(略微模糊)
若是尺寸固定,那么能够直接使用 svg 方式,参考这篇文章:用SVG实现一个优雅的提示框 (juejin.cn)
就目前而言,确实没有比较好的实现方案(有更好的实现方式欢迎补充😂,我暂时想不出来了),不过若是借助 CSS paint ,那一切就都有可能了!只须要在 paint 函数中绘制边框和背景就好了
绘制代码以下
registerPaint('tips-bg', class { paint(ctx, size) { const { width,height } = size; // 容器尺寸 const radius = 5; // 圆角大小 const deg = Math.PI / 2; const edge = 5; // 三角形大小 const pos = width / 2; // 三角形位置 const lineWidth = 2; // 描边宽度 ctx.beginPath(); ctx.moveTo(radius+lineWidth,lineWidth); ctx.lineTo(width-2*radius-lineWidth,lineWidth); ctx.arc(width-radius-lineWidth,radius+lineWidth,radius,-deg,0); ctx.lineTo(width-lineWidth,height-2*radius-edge-lineWidth); ctx.arc(width-radius-lineWidth,height-radius-edge-lineWidth,radius,0,deg); ctx.lineTo(pos+edge,height-edge-lineWidth); ctx.lineTo(pos,height-lineWidth); ctx.lineTo(pos-edge,height-edge-lineWidth); ctx.lineTo(radius+lineWidth,height-edge-lineWidth); ctx.arc(radius+lineWidth,height-radius-edge-lineWidth,radius,deg,2*deg); ctx.lineTo(lineWidth,radius+lineWidth); ctx.arc(radius+lineWidth,radius+lineWidth,radius,-2*deg,-deg); ctx.closePath(); const gradient = ctx.createLinearGradient(0, 0, width, 0); // 渐变背景 gradient.addColorStop(0, '#F57853'); gradient.addColorStop(1, '#F8B578'); ctx.fillStyle = gradient; ctx.fill(); ctx.strokeStyle = '#FBF8F8'; // 绘制边框 ctx.lineWidth = lineWidth; ctx.lineCap = 'round'; ctx.stroke(); } });
tips{ /* -webkit-mask-image: paint(tips-bg); */ background: paint(tips-bg); /*再也不借助mask,纯js绘制背景,包括渐变*/ }
实时效果以下
完整代码可访问 tooltips-paint-stroke (codepen.io)
以上针对 tooltips 布局共介绍了3种不一样类型的实现方式,分别是 clip-path、mask、CSS paint。其中 mask 的实现重点实际上是CSS图形的绘制,主要有 渐变 和 svg 两种,虽然 渐变 的写法稍微复杂一点,可是最为通用,其余方式可能换一种布局就不适用了。如今总结一下要点:
固然,这些方式不只仅是实现本文的布局而已,更多的是提供一种思路,下次碰到其余的“异形布局”也能立刻联想出相应的解决方案,而不是选择“切图.png”。若是以为还不错,对你有帮助的话,欢迎点赞、收藏、转发❤❤❤