CSS 實現支持漸變的提示框(tooltips)

今天來看一種十分常見的交互:提示框(tooltips)。一般提示框都是純色的,好比下面這個css

image

這類佈局實現還不算複雜,能夠用一個圓角矩形和一個小三角拼接造成,設置相同的顏色就能夠了html

image

這個並非本文的重點,有興趣的能夠訪問 css-tips (codepen.io)web

有時候,爲了突出強調產品的特色或者爲了跟隨設計的潮流,設計會用上漸變背景,好比 lulu UI Edge 版本中的 Tips 組件canvas

image-20210607185438690

若是仍然採用「拼接」的方式,不可避免會出現銜接不上的問題,有明顯的「割裂」感,視覺還原會大打折扣api

image

那麼,如何實現這類效果呢?一塊兒來看看吧svg

1、clip-path 裁剪

clip-path 多是不少人立刻就能想到的方式。可是實際操做下來,仍是會遇到不少麻煩wordpress

  1. clip-path: path 能夠支持任意形狀,可是卻無法實現自適應寬高
  2. clip-path: polygon 能夠實現小尖角,可是沒法實現圓角
  3. clip-path: inset 能夠實現自適應圓角矩形,可是沒法實現下方的小尖角

如何解決這個問題呢?其實把 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

image-20210607234448368

接着其中一個裁剪成圓角矩形,另一個裁剪成小三角,而後重疊起來就能夠了

.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個點的座標就能夠了 */
}

能夠看到提示框徹底是自適應的,實時效果以下

image-20210606003706857

完整代碼可訪問 tooltips-clip-path (codepen.io)

2、mask 遮罩

除了 clip-pathmask 也是一種思路。若是還不熟悉 mask,能夠參考這一篇 奇妙的 CSS MASK (juejin.im) 。這裏的原理以下

image-20210607234301475

利用 mask ,如今的問題就轉變成了:如何經過 CSS 繪製這樣一個圖形?

image-20210605181646410

1. 萬能的 gradient

沒有什麼圖形是 CSS 漸變 繪製不出來的,這個也不例外。首先咱們把這個圖形進行分解,這裏能夠分紅一個圓角矩形和一個三角形,三角形比較容易,能夠經過 conic-gradient 或者 linear-gradient 繪製

image-20210605190716575

圓角矩形就稍微有點麻煩了,不過仍是能夠分解的,以下

Kapture 2021-06-05 at 20.08.32

能夠由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,利用背景的平鋪特性,合理設置背景尺寸就能夠了,以下

Kapture 2021-06-05 at 21.19.57

能夠看到,背景尺寸設置成 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;
}

是否是精簡了許多?而後再把三角形的合過來就好了,能夠獲得以下效果

image-20210606003846115

完整代碼可訪問 tooltips-mask-gradient (codepen.io)

2. 自適應的svg

儘管作了一些優化,上面的代碼量仍然很是可觀,有沒有更加簡便的方式呢?

想到了 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 是能夠自適應的,在改變尺寸的狀況下不會變形(注意觀察圓角),以下

image-20210605221224181

三角形就很容易了,能夠用 <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更好的形式

仍是挺不錯的,代碼量也很少,也比較容易理解,實時效果以下

image-20210606004126668

完整代碼可訪問 tooltips-mask-svg (codepen.io)

3、paint 繪製

再來介紹一種將來的解決方式, CSS paint 。關於 CSS paint,又稱 「CSS 界的繪圖板」,簡單來講,就是用 canvas 繪圖的方式來繪製背景,canvas 幾乎什麼都能繪製吧,因此這是一種更爲通用的解決方案。想快速瞭解 CSS paint 的能夠參考這一篇入門文章:CSS屆的繪圖板CSS Paint API簡介,不過目前僅支持 Chrome,兼容性以下

截屏2021-06-07 23.23.28

不過並不影響咱們的學習,畢竟是將來的解決方案,先看看大體的語法,以下

  1. 首先,JS 註冊模塊 registerPaint
// paint-tips.js
registerPaint('tips-bg', class {
    paint(ctx, size, properties) {
       // 在這裏繪製背景,語法和canvas相似
    }
});
  1. 接着,JS 添加模塊 CSS.paintWorklet.addModule
if (window.CSS) {
    CSS.paintWorklet.addModule('paint-tips.js');
}
  1. 最後,CSS 中使用 paint(tips-bg)
tips{
  -webkit-mask-image: paint(tips-bg); /*這裏做爲遮罩背景使用*/
}

下面就來繪製提示框了,若是仍然藉助 mask ,那麼問題就變成了:如何經過 canvas 繪製這樣一個圖形?

image-20210606205856074

在 canvas 中,相對於 CSS 來講, 這類圖形簡直就是小兒科,只須要使用 lineToarc 兩個指令就能夠繪製了。最關鍵的一點是,這裏的尺寸是實時渲染的,能夠經過 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();
   }
});

實時效果以下

image-20210606215036666

完整代碼可訪問 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 額外處理,實時效果以下

Kapture 2021-06-07 at 00.20.01

完整代碼可訪問 tooltips-mask-paint-var (codepen.io)

4、描邊效果

有時候提示框可能還會有描邊的效果,好比這樣的

image-20210607190931300

這類帶描邊的其實以上方式都不太適用,clip-pathmask 都沒法實現描邊,不過有一個邊框生成方案能夠參考:有意思!不規則邊框的生成方案 (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繪製背景,包括漸變*/
}

實時效果以下

image-20210607200501837

完整代碼可訪問 tooltips-paint-stroke (codepen.io)

5、總結和說明

以上針對 tooltips 佈局共介紹了3種不一樣類型的實現方式,分別是 clip-pathmaskCSS paint。其中 mask 的實現重點實際上是CSS圖形的繪製,主要有 漸變svg 兩種,雖然 漸變 的寫法稍微複雜一點,可是最爲通用,其餘方式可能換一種佈局就不適用了。如今總結一下要點:

  1. 能夠用多個容器重疊配合 clip-path 實現複雜的自適應效果
  2. 在使用 CSS 漸變繪製圖形時,相同的形狀充分利用平鋪特性
  3. svg 基本形狀支持百分比尺寸,用做背景一樣有效,可使用多張背景來組合
  4. CSS paint 是將來的最佳解決方式,也能輕易的實現描邊效果
  5. CSS paint 惟一的缺陷是兼容性不夠好(現僅支持 Chrome 65+ ),可是值得學習

固然,這些方式不只僅是實現本文的佈局而已,更多的是提供一種思路,下次碰到其餘的「異形佈局」也能立刻聯想出相應的解決方案,而不是選擇「切圖.png」。若是以爲還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤

相關文章
相關標籤/搜索