CSS 新属性 clip-path
,意味裁剪路径的意思,让我们可以很便捷的生成各种几何图形。
clip-path 通过定义特殊的路径,实现我们想要的图形。而这个路径,正是 SVG 中的 path 。
clip-path 属性api:
/* Keyword values */ clip-path: none; /* Image values */ clip-path: url(resources.svg#c1); /* Box values clip-path: fill-box; clip-path: stroke-box; clip-path: view-box; clip-path: margin-box clip-path: border-box clip-path: padding-box clip-path: content-box /* Geometry values */ /*矩形可以2个值, 也可 4个值 top right bottom left 矩形可以3个值 第一个值 大小 第二值圆角属性【round】 第三个值圆角大小 */ clip-path: inset(100px 50px); /*圆形 第一个值大小 第二值 左右水平位置 第三个上下垂直 位置*/ clip-path: circle(50px at 0 100px); /* */ clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); /* Box and geometry values combined */ clip-path: padding-box circle(50px at 0 100px); /* Global values */ clip-path: inherit; clip-path: initial; clip-path: unset;
例如 clip-path: circle(50px at 50px 50px) 表示在元素的 (50px, 50px)处,裁剪生成一个半径为 50px 的圆,以元素的左上角为坐标起点
而整个 clip-path 属性,最为重要的当属 polygon,可以利用 polygon 生成任意多边形, polygon 几对【x,y】几边形,值可以 是百分比,也可以使用具体的数值。
下面分别列举使用 clip-path 生成一个圆形和一个十边形:
/* 圆形 */ .circle { width: 100px; height: 100px; background-color: yellowgreen; clip-path: circle(50px at 50px 50px); } /* 十边形 */ .polygon { width: 100px; height: 100px; background-color: yellowgreen; /*polygon(x1,y1,x2,y2...)*/ clip-path: polygon(50% 0%, 80% 10%, 100% 35%, 100% 70%, 80% 90%, 50% 100%, 20% 90%, 0% 70%, 0% 35%, 20% 10%); }
clip-path 动画
clip-path 另外一个强大之处在于可以进行 CSS transtion 与 CSS animation,也就是过渡和动画。
<div class="polygon-animate"></div> /* 几何图形变换 polygon 坐标位置可以去 http://bennettfeely.com/clippy/ 获取 如果页面打开过慢 或点击图像选择不了请 使用翻墙软件 */
.polygon-animate { position: absolute; width: 200px; height: 200px; top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); background-color: crimson; -webkit-transition: .3s; transition: .3s; -webkit-clip-path: polygon(50% 0%, 0% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%); clip-path: polygon(50% 0%, 0% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%); -webkit-animation: polygon-ani 5s linear infinite; animation: polygon-ani 5s linear infinite; } @-webkit-keyframes polygon-ani { 10% { background-color: darkorange; -webkit-clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%); } 14% { -webkit-clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%); } 24% { background-color: lemonchiffon; -webkit-clip-path: polygon(100% 38%, 82% 100%, 82% 100%, 18% 100%, 0% 38%, 0% 38%, 0% 38%, 0% 38%, 50% 0%); clip-path: polygon(100% 38%, 82% 100%, 82% 100%, 18% 100%, 0% 38%, 0% 38%, 0% 38%, 0% 38%, 50% 0%); } 28% { -webkit-clip-path: polygon(100% 38%, 82% 100%, 82% 100%, 18% 100%, 0% 38%, 0% 38%, 0% 38%, 0% 38%, 50% 0%); clip-path: polygon(100% 38%, 82% 100%, 82% 100%, 18% 100%, 0% 38%, 0% 38%, 0% 38%, 0% 38%, 50% 0%); } 38% { background-color: darkturquoise; -webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 100% 75%, 50% 100%, 0% 75%, 0% 75%, 0% 25%, 0% 25%); clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 100% 75%, 50% 100%, 0% 75%, 0% 75%, 0% 25%, 0% 25%); } 42% { -webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 100% 75%, 50% 100%, 0% 75%, 0% 75%, 0% 25%, 0% 25%); clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 100% 75%, 50% 100%, 0% 75%, 0% 75%, 0% 25%, 0% 25%); } 52% { background-color: darkcyan; -webkit-clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 25% 100%, 0% 60%, 10% 20%, 50% 0%); clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 25% 100%, 0% 60%, 10% 20%, 50% 0%); } 56% { -webkit-clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 25% 100%, 0% 60%, 10% 20%, 50% 0%); clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 25% 100%, 0% 60%, 10% 20%, 50% 0%); } 66% { background-color: deepskyblue; -webkit-clip-path: polygon(30% 0%, 70% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); clip-path: polygon(30% 0%, 70% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); } 70% { -webkit-clip-path: polygon(30% 0%, 70% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); clip-path: polygon(30% 0%, 70% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); } 80% { background-color: indigo; -webkit-clip-path: polygon(83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%, 50% 0%); clip-path: polygon(83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%, 50% 0%); } 84% { -webkit-clip-path: polygon(83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%, 50% 0%); clip-path: polygon(83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%, 50% 0%); } 94% { background-color: crimson; -webkit-clip-path: polygon(50% 0%, 0% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%); clip-path: polygon(50% 0%, 0% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%); } } @keyframes polygon-ani { 10% { background-color: darkorange; -webkit-clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%); } 14% { -webkit-clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%, 0% 50%); } 24% { background-color: lemonchiffon; -webkit-clip-path: polygon(100% 38%, 82% 100%, 82% 100%, 18% 100%, 0% 38%, 0% 38%, 0% 38%, 0% 38%, 50% 0%); clip-path: polygon(100% 38%, 82% 100%, 82% 100%, 18% 100%, 0% 38%, 0% 38%, 0% 38%, 0% 38%, 50% 0%); } 28% { -webkit-clip-path: polygon(100% 38%, 82% 100%, 82% 100%, 18% 100%, 0% 38%, 0% 38%, 0% 38%, 0% 38%, 50% 0%); clip-path: polygon(100% 38%, 82% 100%, 82% 100%, 18% 100%, 0% 38%, 0% 38%, 0% 38%, 0% 38%, 50% 0%); } 38% { background-color: darkturquoise; -webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 100% 75%, 50% 100%, 0% 75%, 0% 75%, 0% 25%, 0% 25%); clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 100% 75%, 50% 100%, 0% 75%, 0% 75%, 0% 25%, 0% 25%); } 42% { -webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 100% 75%, 50% 100%, 0% 75%, 0% 75%, 0% 25%, 0% 25%); clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 100% 75%, 50% 100%, 0% 75%, 0% 75%, 0% 25%, 0% 25%); } 52% { background-color: darkcyan; -webkit-clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 25% 100%, 0% 60%, 10% 20%, 50% 0%); clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 25% 100%, 0% 60%, 10% 20%, 50% 0%); } 56% { -webkit-clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 25% 100%, 0% 60%, 10% 20%, 50% 0%); clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 25% 100%, 0% 60%, 10% 20%, 50% 0%); } 66% { background-color: deepskyblue; -webkit-clip-path: polygon(30% 0%, 70% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); clip-path: polygon(30% 0%, 70% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); } 70% { -webkit-clip-path: polygon(30% 0%, 70% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); clip-path: polygon(30% 0%, 70% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); } 80% { background-color: indigo; -webkit-clip-path: polygon(83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%, 50% 0%); clip-path: polygon(83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%, 50% 0%); } 84% { -webkit-clip-path: polygon(83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%, 50% 0%); clip-path: polygon(83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%, 50% 0%); } 94% { background-color: crimson; -webkit-clip-path: polygon(50% 0%, 0% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%); clip-path: polygon(50% 0%, 0% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%); } }
除此之外,我们还可以尝试,将一个完整的图形,分割成多个小图形
<hgroup class="triangle2rect"> <div class="a"></div> <div class="b"></div> <div class="c"></div> <div class="d"></div> </hgroup>
.triangle2rect { position: absolute; width: 100px; height: 100px; top: 50%; left: 50%; transform: translate(-50%, -50%); animation: aniContainer 2s infinite alternate; } .triangle2rect div { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .a { background: deeppink; clip-path: polygon(0% 0%, 0% 100%, 50% 50%); animation: a 2s infinite alternate; } .b { background: deeppink; clip-path: polygon(0% 0%, 100% 0%, 50% 50%); animation: b 2s infinite alternate; } .c { background: deeppink; clip-path: polygon(100% 0%, 100% 100%, 50% 50%); animation: c 2s infinite alternate; } .d { background: deeppink; clip-path: polygon(100% 100%, 0% 100%, 50% 50%); animation: d 2s infinite alternate; } @keyframes a { 0%, 10% { background: deeppink; clip-path: polygon(0% 0%, 0% 100%, 50% 50%); } 90%, 100% { background: #000; clip-path: polygon(0% 100%, 25% 100%, 12.5% 0%); } } @keyframes b { 0%, 10% { background: deeppink; clip-path: polygon(0% 0%, 100% 0%, 50% 50%); } 90%, 100% { background: #000; clip-path: polygon(25% 0%, 50% 0%, 37.5% 100%); } } @keyframes c { 0%, 10% { background: deeppink; clip-path: polygon(100% 0%, 100% 100%, 50% 50%); } 90%, 100% { background: #000; clip-path: polygon(62.5% 0%, 75% 100%, 50% 100%); } } @keyframes d { 0%, 10% { background: deeppink; clip-path: polygon(100% 100%, 0% 100%, 50% 50%); } 90%, 100% { background: #000; clip-path: polygon(100% 0%, 87.5% 100%, 75% 0%); } } @keyframes aniContainer { 0%, 10% { width: 100px; height: 100px; } 90%, 100% { width: 250px; height: 60px; } }
clip-path 动画的局限
clip-path 动画虽然美好,但是存在一定的局限性,那就是进行过渡的两个状态,坐标顶点的数量必须一致。
也就是如果我希望从三角形过渡到矩形。假设三角形和矩形的 clip-path
分别为:
- 三角形:
clip-path: polygon(50% 0, 0 100%, 100% 0)
- 矩形:
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%)
进行过渡动画时候,直接从 polygon(50% 0, 0 100%, 100% 0)
--> polygon(0 0, 100% 0, 100% 100%, 0 100%)
是不行的,因为是从 3 个坐标点变换到 4 个坐标点。
因此这里需要这用一个讨巧的办法,在三角形的表示方法中,使用四个坐标点表示,其中两个坐标点进行重合即可。也就是:
- 三角形:
clip-path: polygon(50% 0, 0 100%, 100% 0)
->clip-path: polygon(50% 0, 50% 0, 0 100%, 100% 0)
N边形过渡动画
如果脑洞够大,随机生成 N(N>=1000)边形 //只是随机生成了 2000 个坐标点,然后使用 clip-path
将这些坐标点连接起来,并不是符合要求的多边形
https://codepen.io/Chokcoco/pen/XgJRzO?editors=1010
<div></div>
div { width: 300px; height: 300px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); transition: all .5s; transition-timing-function: cubic-bezier(.92,-0.5,1,.12); border-radius: 50%; }
1 setInterval(function() { 2 const length = 2000; 3 4 let el = document.querySelectorAll("div")[0]; 5 let coordinate = ""; 6 7 for (let i = 0; i < length; i++) { 8 coordinate += 9 parseInt(Math.random() * 10000) / 100 + 10 "% " + 11 parseInt(Math.random() * 10000) / 100 + 12 "%, "; 13 } 14 15 coordinate = "polygon(" + coordinate.slice(0, -2) + ")"; 16 17 el.style.clipPath = coordinate; 18 el.style.backgroundColor = 19 "#" + (~~(Math.random() * (1 << 24))).toString(16); 20 }, 500);
改良后的 (VUE官网) https://codepen.io/Chokcoco/pen/NgqGOo?editors=1111
1 <div id="app"> 2 <!-- 使用 clip-path --> 3 <div id="svgpolygon" v-bind:style="styleObject"></div> 4 <label>Sides: {{ sides }}</label> 5 <input type="range" min="3" max="500" v-model.number="sides"> 6 <label>Minimum Radius: {{ minRadius }}%</label> 7 <input type="range" min="0" max="90" v-model.number="minRadius"> 8 <label>Update Interval: {{ updateInterval }} milliseconds</label> 9 <input type="range" min="10" max="2000" v-model.number="updateInterval"> 10 </div>
1 #svgpolygon { 2 display: block; 3 width: 180px; 4 height: 180px; 5 border-radius: 50%; 6 border: 1px solid #333; 7 } 8 9 input[type="range"] { 10 display: block; 11 width: 100%; 12 margin-bottom: 15px; 13 }
1 new Vue({ 2 el: "#app", 3 data: function() { 4 var defaultSides = 500; 5 var stats = Array.apply(null, { length: defaultSides }).map(function() { 6 return 100; 7 }); 8 return { 9 stats: stats, 10 points: generatePoints(stats), 11 sides: defaultSides, 12 minRadius: 50, 13 interval: null, 14 updateInterval: 500 15 }; 16 }, 17 computed: { 18 styleObject: function() { 19 return { 20 background: '#41B883', 21 'clip-path': 'polygon(' + this.points +')' 22 } 23 } 24 }, 25 watch: { 26 sides: function(newSides, oldSides) { 27 var sidesDifference = newSides - oldSides; 28 if (sidesDifference > 0) { 29 for (var i = 1; i <= sidesDifference; i++) { 30 this.stats.push(this.newRandomValue()); 31 } 32 } else { 33 var absoluteSidesDifference = Math.abs(sidesDifference); 34 for (var i = 1; i <= absoluteSidesDifference; i++) { 35 this.stats.shift(); 36 } 37 } 38 }, 39 stats: function(newStats) { 40 TweenLite.to(this.$data, this.updateInterval / 1000, { 41 points: generatePoints(newStats) 42 }); 43 }, 44 updateInterval: function() { 45 this.resetInterval(); 46 } 47 }, 48 mounted: function() { 49 this.resetInterval(); 50 }, 51 methods: { 52 randomizeStats: function() { 53 var vm = this; 54 this.stats = this.stats.map(function() { 55 return vm.newRandomValue(); 56 }); 57 }, 58 newRandomValue: function() { 59 return Math.ceil( 60 this.minRadius + Math.random() * (100 - this.minRadius) 61 ); 62 }, 63 resetInterval: function() { 64 var vm = this; 65 clearInterval(this.interval); 66 this.randomizeStats(); 67 this.interval = setInterval(function() { 68 vm.randomizeStats(); 69 }, this.updateInterval); 70 } 71 } 72 }); 73 74 function valueToPoint(value, index, total) { 75 var x = 0; 76 var y = -value * 0.9; 77 var angle = Math.PI * 2 / total * index; 78 var cos = Math.cos(angle); 79 var sin = Math.sin(angle); 80 var tx = x * cos - y * sin + 100; 81 var ty = x * sin + y * cos + 100; 82 return { x: tx, y: ty }; 83 } 84 85 function generatePoints(stats) { 86 var total = stats.length; 87 var points = stats 88 .map(function(stat, index) { 89 var point = valueToPoint(stat, index, total); 90 return point.x + "px " + point.y +"px,"; 91 }) 92 .join(" "); 93 94 points = points.slice(0, -1); 95 96 return points; 97 }