一、前言
灵动岛(Dynamic Island )是什么?
灵动岛,是苹果公司iPhone 14 Pro系列 [2]
交互UI,让虚拟软件和硬件的交互变得更为流畅。当有来电、短信等通知时,灵动岛会变化它的形态,以便让用户能够更直观地接收到这些信息。而在用户使用一些应用App并将其切换到后台时(如音乐),灵动岛也能以另一种形态来显示这些软件,还可以轻点这个区域进行更复杂一点的操作,比如切换歌曲。
功能
当用户收到信息后,iPhone 14 Pro显示屏上方的灵动岛可以展开显示信息。此外灵动岛还可以显示音乐播放、Siri等组件,让用户在首页直接完成各种功能控制和信息阅读。iPhone 14 Pro 拥有6.1英寸屏幕,还将推出6.7英寸的iPhone 14 Pro MAX [2]
发展历程
2022年9月,iOS 16.1 Beta 1 发布后,苹果iPhone 14 Pro和Max灵动岛已支持单手操作。 [4]
2022年10月,媒体报道,在一次新的采访中,苹果公司软件工程高级副总裁 Craig Federighi 和苹果公司人机界面设计副总裁 Alan Dye,讨论了 iPhone 14 Pro 的灵动岛。表示 iPhone X 问世五年来的第一个重大操作变化。
当用户收到信息后,iPhone 14 Pro显示屏上方的灵动岛可以展开显示信息。此外灵动岛还可以显示音乐播放、Siri等组件,让用户在首页直接完成各种功能控制和信息阅读。iPhone 14 Pro 拥有6.1英寸屏幕,还将推出6.7英寸的iPhone 14 Pro MAX [2]
我主要实现的是从灵动岛跳转csdn主页的动画效果,大家有需要可以在html部分更改跳转的链接(iframe部分)
%iframe#doom{:height => "800", :src => "https://blog.csdn.net/weixin_43233219?spm=1019.2139.3001.5343/", :width => "1200"}
tips:但是该代码的缺点是目前该跳转链接和灵动岛的大小以及Ui不匹配
二、效果展示1
三、编码实现1
html部分
#wrapper .phone .inner .info %span#time %span .island .framewrap %iframe#doom{:height => "800", :src => "https://blog.csdn.net/weixin_43233219?spm=1019.2139.3001.5343/", :width => "1200"}
css部分
body { display: flex; justify-content: center; align-items: center; height: 100vh; width: 100vw; position: relative; overflow: hidden; background: #000; --transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.075); perspective: 2400px; &:after { content: "Enter : Start // Arrow Keys : Move // Ctrl : Shoot // Space : Open Door"; color: #666; position: absolute; top: 10px; font-size: 14px; transition: var(--transition); transform: translateY(-50px); } &:before { content: ""; position: absolute; width: 100vw; height: calc(50vh - 100px); bottom: 0; left: 0; z-index: 12; pointer-events: none; background: linear-gradient(to top, #000, rgba(0,0,0,0.01)); } &.loaded { .phone { transform: translateY(0); filter: brightness(1); } } #wrapper { position: absolute; width: 100vw; height: 100vh; transition: transform 0.5s cubic-bezier(0.95, 0.84, 0.44, 1); transform-style: preserve-3d; transform: rotateX(30deg) translateZ(0px); transform-origin: 50% 75%; } &:hover { #wrapper { transform: rotateX(30deg) translateZ(0px) scale(1.015); } } &:not(.active){ .phone .island{ animation:pulse 10s ease-in-out infinite; @keyframes pulse{ 90%{ box-shadow:0 0 0 0px rgba(0,0,0,0.01); } 95%{ box-shadow:0 0 0 3px rgba(0,0,0,0.25); } 100%{ box-shadow:0 0 0 8px rgba(0,0,0,0.01); } } } } &.active { &:after { transform: translateY(0); transition-delay: 0.5s; } #wrapper { transform: scale(1.25) translateZ(0px); transition:transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.075); } .phone { .island { &:after { transform: scaleX(3.5) scaleY(9.85) translateZ(0px); border-radius: 6px / 2px; } } &:before { filter: contrast(2); } .info { width: 150%; transition-delay: 0.05s; } } .framewrap { transform: translate(-50%, calc(-50% - 15px)) scale(1) translateZ(0px); iframe { opacity: 1; } } } .framewrap { position: absolute; left: 50%; top: 50%; transform: translate(-50%, calc(-50% - 15px)) scale(0) translateZ(0px); transform-origin: 50% calc(50% - 100px); width: 1000px; height: 210px; overflow: hidden; transition: var(--transition); z-index: 20; &:before { content: ""; position: absolute; width: 330px; height: 210px; border-radius: 15px; box-shadow: inset 0 0 0 5px #000, 0 0 0 5px #000; z-index: 10; pointer-events: none; left: 50%; top: 50%; transform: translate(-50%, -50%); } } .phone { position: absolute; width: 400px; height: 700px; background: url("https://assets.codepen.io/383755/wallpaper.jpg") 50% 50% / cover, #000; border-radius: 50px; left: calc(50% - 200px); top: calc(50% - 160px); transform: scale(1.25) translateY(100vh) rotateX(-10deg); transition: 2s cubic-bezier(0.175, 0.885, 0.32, 1.075); filter: brightness(1.5); .inner { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: inherit; overflow: hidden; } &:before { content: ""; position: absolute; width: 100%; height: 100%; border-radius: inherit; left: 0; top: 0; box-shadow: inset 0 0 0 2px #666, inset 0 0 0 12.5px #000, 0 0 0 1px #fff; z-index: 2; transition: var(--transition); } &:after { content: ""; position: absolute; width: 2px; height: 40px; background: #efefef; left: -3px; top: 125px; box-shadow: 0 75px 0 #efefef, 0 100px 0 #efefef; } .info { position: absolute; width: calc(100% - 100px); top: 25px; left: 50%; transform: translateX(-50%); z-index: 1; display: flex; justify-content: space-between; color: #fff; transition: 0.175s cubic-bezier(0.175, 0.885, 0.32, 1.075); transition-delay: 0.3s; span:not(#time) { &:before, &:after { content: ""; position: absolute; top: 0; } &:before { width: 25px; height: 15px; right: 20px; background: linear-gradient( to right, #fff 3px, rgba(0,0,0,0.01) 3px, rgba(0,0,0,0.01) 10px ) 0px 100% / 20px 5px no-repeat, linear-gradient( to right, #fff 3px, rgba(0,0,0,0.01) 3px, rgba(0,0,0,0.01) 10px ) 5px 100% / 20px 7.5px no-repeat, linear-gradient( to right, #fff 3px, rgba(0,0,0,0.01) 3px, rgba(0,0,0,0.01) 10px ) 10px 100% / 20px 10px no-repeat, linear-gradient( to right, #fff 3px, rgba(0,0,0,0.01) 3px, rgba(0,0,0,0.01) 10px ) 15px 100% / 20px 12.5px no-repeat; } &:after { width: 22.5px; height: 13px; right: 0px; background: radial-gradient( circle at bottom, #fff 2px, rgba(0,0,0,0.01) 2px, rgba(0,0,0,0.01) 5px, #fff 5px, #fff 7px, rgba(0,0,0,0.01) 7px, rgba(0,0,0,0.01) 10px, #fff 10px, #fff 12px, rgba(0,0,0,0.01) 12px ); clip-path: polygon(0 0, 50% 100%, 100% 0); } } } .island { width: 100px; height: 25px; position: absolute; top: 20px; left: 50%; transform: translateX(-50%); cursor: pointer; z-index: 9; border-radius: 50px; &:before, &:after { content: ""; position: absolute; } &:before { width: 12.5px; height: 12.5px; border-radius: 100%; box-shadow: inset 0 0 2px 0.5px #333, 0 0 0 1px #111; top: 5px; right: 10px; z-index: 2; background: radial-gradient(circle at center, #222 1px, #000 1.5px) 1px 0px / 100% 100% no-repeat; } &:after { width: 100%; height: 100%; border-radius: 50px; background: #000; top: 0; left: 0; transition: var(--transition); transform-origin: top; } } } iframe { clip-path: polygon(10px 207px, 650px 207px, 650px 612px, 10px 612px); height: 800px; width: 1200px; position: absolute; left: 50%; top: 50%; transform: translate(calc(-50% + 136.5px), calc(-50% - 5px)) scale(0.5); opacity: 0; transition: var(--transition); } }
js部分
document.querySelector(".island").addEventListener("click", function () { document.body.classList.toggle("active"); }); document.addEventListener( "DOMContentLoaded", function () { var today = new Date(); var time = (today.getHours() % 12) + ":" + today.getMinutes().toString().padStart(2, "0"); document.getElementById("time").appendChild(document.createTextNode(time)); setTimeout(() => { document.body.classList.add("loaded"); }, 2000); }, false );
四、效果展示2
再增加一个前端灵动岛的ui效果源码,可以 切换界面的Ui主题颜色,有官方的几个配色和随机配色
效果图如下
五、源码2
.html代码
input(name="theme" type="radio" id="deep-purple" checked) input(name="theme" type="radio" id="gold") input(name="theme" type="radio" id="space-black") input(name="theme" type="radio" id="silver") input(name="theme" type="radio" id="random") input(name="zoom" type="checkbox" id="zoom") .scene .pallette label.swatch(tabIndex="0" for="deep-purple") label.swatch(tabIndex="0" for="gold") label.swatch(tabIndex="0" for="space-black") label.swatch(tabIndex="0" for="silver") label.swatch(tabIndex="0" for="random") .icon .tooltip CMD + click to save .zoom-con label.swatch(tabIndex="0" for="zoom") .phone-con .phone .buttons .left .button .button .button .right .button .camera .screen-container .bg .deep-purple .section .glow .section .glow .gold .section .glow .section .glow .space-black .section .glow .section .glow .silver .section .glow .section .glow .random.canvas .shapes .shape .shape .shape .shape .shape .shape .notch-container(tabIndex="0") .notch .content .left .tile .text .right .bar .duration .notch-blur .screen .app .weather .app .app .app .app .app .app .app .app .app
.css代码
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400&display=swap'); :root { --size: max(5px, 1vmin); --height: 80em; --pad: 1.25em; --border-radius: 6.666em; --gutter: calc(var(--pad) * 2); --scene-pad: 5vmin; --bg-blur: 0.333em; --button-width: 0.333em; --notch-height: 3.33em; --notch-width: 33.3%; --notch-radius: calc(var(--border-radius) - calc(var(--pad) * 2)); --notch-duration: 0.333s; --ease: cubic-bezier(.666, 0, .4, 1); --ease-spring: cubic-bezier(.666, 0, .4, 1.2); --ease-out: cubic-bezier(.15,0,.333,1); --border-width: 0.4em; --deep-purple: 284; --gold: 22.5; --space-black: 215; --silver: 254; --c-h: var(--deep-purple); --c-s: 100%; --c-l: 50%; } // Squircle effect @function round-off() { @return url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1'><defs><filter id='round'><feGaussianBlur in='SourceGraphic' stdDeviation='5' result='blur' /><feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9' result='goo'/><feComposite in='SourceGraphic' in2='goo' operator='atop'/></filter></defs></svg>#round"); } // Smoother gradients @function scrim-gradient( $startColor, $direction: 'to bottom', $from: 0, $to: 100, $type: linear, $reverse: false, $debug: false ) { $scrimCoordinates: ( 0: 1, 19: 0.738, 34: 0.541, 47: 0.382, 56.5: 0.278, 65: 0.194, 73: 0.126, 80.2: 0.075, 86.1: 0.042, 91: 0.021, 95.2: 0.008, 98.2: 0.002, 100: 0 ); $hsl: ''; @if type-of($startColor) == 'list' { $hsl: $startColor; } @else { $hue: hue($startColor); $saturation: saturation($startColor); $lightness: lightness($startColor); $hsl: #{$hue}, #{$saturation}, #{$lightness}; } $stops: (); @each $colorStop, $alpha in $scrimCoordinates { $stop: unquote("hsla(#{$hsl}, #{if($reverse, 1 - $alpha, $alpha)})") $from + ($colorStop/($to/(100 - $from))) * 1%; $stops: append($stops, $stop, comma); } @if $debug { @return '$startColor: #{$startColor},$direction: #{$direction}, $from: #{$from}, $to: #{$to}, $type: #{$type}, $reverse: #{$reverse}'; } @else { @return #{$type}-gradient(unquote($direction), $stops); } } @keyframes appear { to { transform: scale3d(1,1,1); opacity: 1; } } body { background: darken(blue, 48); } .scene { display: flex; flex-wrap: wrap; gap: 3em 0; align-items: center; align-content: center; justify-content: center; min-height: 100vh; font-family: Inter; font-size: var(--size); padding: var(--scene-pad); box-sizing: border-box; } .phone-con { flex-basis: 100%; display: flex; justify-content: center; } .phone { position: relative; z-index: 1; aspect-ratio: 37/76; background: black; height: var(--height); border-radius: var(--border-radius); box-shadow: 0 0 0.1em 0.25em hsl(var(--c-h), 20%, 25%), 0 0 0 var(--border-width) hsl(var(--c-h), 30%, 85%); box-sizing: border-box; opacity: 0; transform: scale3d(1.1,1.1,1); animation: appear 1s var(--ease-out) forwards; webkit-backface-visibility: hidden; &:before { content: ''; position: absolute; top: var(--border-radius); right: calc(var(--border-width) * -1); bottom: var(--border-radius); left: calc(var(--border-width) * -1); border: 0.5em solid hsl(var(--c-h), 20%, 30%); border-left-width: 0; border-right-width: 0; } } .buttons { position: absolute; inset: calc(var(--border-width) * -1); pointer-events: none; .left, .right { position: absolute; width: var(--button-width); display: flex; flex-direction: column; align-items: stretch; gap: 1.5em; } .left { right: 100%; top: calc(var(--border-radius) * 2); .button { &:nth-child(1) { height: 3em; margin-bottom: 0.5em; } } } .right { left: 100%; transform: scale3d(-1, 1, 1); top: calc(var(--border-radius) * 3); .button { height: 9.5em; } } .button { background: hsl(var(--c-h), 20%, 95%); height: 6em; box-shadow: inset -0.15em 0 0.1em black, inset 0 0 0.1em hsl(var(--c-h), 30%, 90%), inset 0 0.2em 0.1em hsl(var(--c-h), 30%, 90%), inset 0 -0.2em 0.1em hsl(var(--c-h), 30%, 90%), inset -0.1em 0.333em 0.1em rgba(black, 0.5), inset -0.1em -0.333em 0.1em rgba(black, 0.5), ; border-top-left-radius: 0.2em; border-bottom-left-radius: 0.2em; } } .screen-container { position: absolute; // overflow: hidden; inset: 0; border-radius: var(--border-radius); border: var(--pad) solid black; display: flex; flex-direction: column; align-items: center; gap: calc(var(--pad) * 2); // Bottom thingy &:before { content: ''; position: absolute; z-index: 2; background: white; width: 36.6%; bottom: calc(var(--pad) * 0.75); height: calc(var(--pad) * 0.5); border-radius: calc(var(--pad) * 0.25); filter: drop-shadow(0 0.1em 0.25em rgba(black, 0.1)); } } .bg { position: absolute; inset: 0; background: black; border-radius: calc(var(--border-radius) - var(--pad)); overflow: hidden; // filter: round-off(); transform: translateZ(0); > * { position: absolute; inset: 0; display: flex; flex-direction: column; opacity: 0; transition: opacity 1s var(--ease-out) 0.5s; background: black; } .section { --g-h: var(--c-h); --g-s: var(--c-s); --g-l: var(--c-l); flex-grow: 1; position: relative; overflow: hidden; border-radius: calc(var(--border-radius) - var(--pad)); border-bottom-left-radius: 20em; border-bottom-right-radius: 20em; &:before { content: ''; position: absolute; inset: 0; border-radius: inherit; background: scrim-gradient(black, '120% 110% at 50% 92.5%', $from: 33.3, $type: radial), scrim-gradient((calc(var(--g-h) - var(--g-hue-adjust-2, var(--g-hue-adjust))), 100%, 50%), '100% 66.6% at 110% var(--g-hue-adjust-2-y, 100%)', $from: 33.3, $type: radial), scrim-gradient((calc(var(--g-h) - var(--g-hue-adjust-2, var(--g-hue-adjust))), 100%, 50%), '100% 66.6% at -10% var(--g-hue-adjust-2-y, 100%)', $from: 33.3, $type: radial), scrim-gradient((calc(var(--g-h) + 33.3), 100%, var(--g-lightness, 82.5%)), '150% 100% at 50% 80%', $from: 35, $type: radial, $reverse: true), ; background-color: hsl(var(--g-h), var(--g-s), var(--g-l)); transform: scale3d(1.1, 1.25, 1); transform-origin: bottom; transition: transform 1s var(--ease-out) 0.5s; } &:after { content: ''; position: absolute; inset: 0; border: var(--border-width) solid rgba(white, 0.8); border-radius: inherit; filter: blur(0.05em); // backdrop-filter: blur(0.75em); mask-image: radial-gradient(100% 100% at 50% 70%, black 30%, transparent 50%); transform: translatez(2px); } .glow { position: absolute; inset: 0; border-radius: inherit; mix-blend-mode: overlay; z-index: 1; background: radial-gradient(80% 150% at 50% 100%, hsl(var(--g-h), 100%, var(--g-l)), transparent 70%); } } // Bottom bg section .section:last-of-type { --g-h: calc(var(--c-h) - var(--g-hue-adjust, 0)); --g-l: calc(var(--c-l) + 40%); --g-lightness: 95%; transform: scale3d(1, -1, 1) translateZ(1px); } } .notch-container { position: absolute; z-index: 3; top: var(--pad); right: var(--pad); left: var(--pad); display: flex; justify-content: center; height: 100%; max-height: calc(var(--notch-radius) * 2); pointer-events: none; outline: none; transition: var(--notch-duration) var(--ease); transition-property: max-height, max-width, filter, transform; will-change: max-width, max-height, filter; // Stop transitions when resizing screen or zooming .is-resizing &, .is-resizing & * { transition: none; } &:hover, &:focus-within { --shadow-opacity: 0.5; transition-timing-function: var(--ease-spring); .content { --content-padding: 2em; .text { opacity: 1; } } .notch { max-width: 100%; max-height: 100%; // transition: max-width 0.3s ease-in-out, max-height 0.2s ease-in-out 0.1s; pointer-events: all; transform: scale3d(1,1,1); } ~ .notch-blur { opacity: 1; max-height: calc(var(--notch-radius) * 3.333 + var(--pad)); } } &:focus-within { max-height: calc(var(--notch-radius) * 3); --bar-height: 1em; --bar-opacity: 1; .left, .right { max-height: calc(100% - var(--bar-height, 0%) - var(--content-gap)); } ~ .notch-blur { max-height: calc(var(--notch-radius) * 5); opacity: 1; } } } // Blurred drop-shadow .notch-blur { position: absolute; z-index: 2; top: calc(var(--pad) - 3px); right: calc(var(--pad) - 3px); left: calc(var(--pad) - 3px); height: 100%; max-height: calc(var(--notch-radius) * 1.5); backdrop-filter: blur(0.2em); mask-image: scrim-gradient(black, $from: 60); opacity: 0; border-radius: calc(var(--border-radius) - var(--pad)); transition: var(--notch-duration) var(--ease); transition-property: max-height, max-width, opacity, transform; will-change: max-width, max-height; } .notch { position: relative; border-radius: var(--notch-radius); pointer-events: all; overflow: hidden; color: white; // max-height: var(--notch-height); // max-width: var(--notch-width); display: flex; cursor: pointer; width: 100%; // max-width: var(--notch-width); transition: inherit; transition-property: inherit; will-change: inherit; filter: drop-shadow(0 1em 2em hsla(0 0% 0% / var(--shadow-opacity, 0))); transform: scale3d(0.375,0.4,1); transform-origin: top; &:before { content: ''; position: absolute; inset: 0; background: black; filter: round-off(); border-radius: inherit; } } .content { --content-padding: 1.75em; --duration-height: 0.5em; --content-gap: 1em; width: 100%; display: flex; flex-wrap: wrap; align-items: stretch; justify-content: stretch; padding: var(--content-padding); gap: var(--content-gap); font-size: 125%; transition-property: padding; will-change: padding; position: relative; .left, .right { height: 100%; max-height: calc(100% - var(--bar-height, 0%)); display: flex; align-items: center; gap: 1em; } &, .left, .right, .bar, .text { transition: var(--notch-duration) var(--ease-out); } .left, .right, .bar { transition-property: max-height, opacity; will-change: max-height; } .left { flex-grow: 2; // background: red; } .text { display: flex; flex-direction: column; gap: 0.333em; transition-property: opacity; opacity: var(--bar-opacity, 0); &:before { content: 'The Move'; order: 1; text-transform: uppercase; } &:after { order: 2; content: 'Space Rangers'; opacity: 0.5; } } .right { flex-grow: 1; // background: blue; } .tile { background: #{desaturate(darken(blue, 10), 25%)}; height: 100%; aspect-ratio: 1; border-radius: 33.3%; position: relative; &:before { content: ''; position: absolute; inset: 0; border-radius: 50%; background: radial-gradient(50% 50% at 55% 40%, desaturate(darken(blue, 20), 25%), #cd1385 75%, transparent) center / 133.3% 133.3%; background-color: yellow; filter: blur(0.05em); } } .bar { display: flex; align-items: center; gap: 1em; flex-basis: 100%; height: 100%; max-height: var(--bar-height, 0%); color: rgba(white, 0.5); opacity: var(--bar-opacity, 0); .duration { position: relative; height: var(--duration-height); background: rgba(white, 0.25); border-radius: calc(var(--duration-height) * 0.5); overflow: hidden; flex-grow: 1; &:before { content: ''; height: 100%; background: white; width: 25%; position: absolute; } } &:before { content: '1:20'; } &:after { content: '-1:48'; } } } .camera { display: flex; justify-content: center; align-items: center; height: var(--notch-height); aspect-ratio: 1/1; border-radius: 50%; pointer-events: none; position: absolute; z-index: 4; top: calc(var(--pad) * 2); right: calc(50% - calc(var(--notch-width) * 0.5)); margin-right: calc(var(--pad) * 0.333); &:before { content: ""; height: 33.3%; aspect-ratio: 1; border-radius: inherit; box-shadow: inset 0 0 0.25em #4c4da3 ; background: radial-gradient(#6667ac, transparent 50%) no-repeat 33.3% 10% / 75% 50%, radial-gradient(darken(#6667ac, 15), transparent 50%) no-repeat 60% 85% / 50% 50%, ; background-color: #080928; } } .screen { // opacity: 0; display: flex; flex-wrap: wrap; align-content: flex-start; flex-grow: 1; gap: var(--gutter); box-sizing: border-box; width: 100%; position: relative; overflow: hidden; z-index: 1; padding: var(--gutter); padding-top: calc(var(--gutter) * 3); border-radius: calc(var(--border-radius) - var(--pad)); transition: opacity 1s var(--ease-out) 0.25s; } .app { --col: 4; aspect-ratio: 1; border-radius: 20%; flex-basis: 15%; flex-grow: 1; display: flex; flex-direction: column; justify-content: space-between; gap: 2%; padding: 5%; box-sizing: border-box; font-size: 1.5em; --scale: 1.5; --duration: 0.8s; transform: scale3d(var(--scale), var(--scale), 1); animation: appear var(--duration) var(--ease-out) forwards; &:nth-child(1), &:nth-child(2) { flex-basis: 40%; border-radius: 15%; background: linear-gradient(190deg, var(--app-bg-s1, white) 33.3%, var(--app-bg-s2, var(--app-bg-s1, white))) top / 100% 125%; } &:not(:nth-child(1)):not(:nth-child(2)) { &:before { content: ''; background: linear-gradient(190deg, var(--app-bg-s1, white), var(--app-bg-s2, var(--app-bg-s1, white))) top / 100% 125%; border-radius: inherit; position: absolute; inset: 0; filter: round-off(); } } &:nth-child(1), &:nth-child(2), &:nth-child(3), &:nth-child(6), &:nth-child(7), &:nth-child(10) { --scale: 1.75; --duration: 1s; } &:nth-child(1) { --app-bg-s1: #{darken(#6490d2, 10)}; --app-bg-s2: #6490d2; color: white; transform-origin: 125% 200%; &:after { content: 'Sunny'; } .weather { display: flex; flex-direction: column; gap: 2%; &:before { content: 'Oakland'; } &:after { font-size: 225%; content: '80°'; } } } &:nth-child(2) { --app-bg-s1: #cbe2ae; --app-bg-s2: #{lighten(#cbe2ae, 15)}; transform-origin: -25% 200%; &:before { content: ''; align-self: flex-end; width: 40%; background: #f8d7a2; border: var(--border-width) solid white; aspect-ratio: 1; border-radius: 50%; } &:after { content: 'Hudson Ave'; font-weight: 500; margin-top: auto; } } &:nth-child(3) { --app-bg-s1: #a7f88f; --app-bg-s2: #41c144; transform-origin: 175% 200%; } &:nth-child(4) { transform-origin: 75% 230%; } &:nth-child(5) { --app-bg-s1: #cecdd5; --app-bg-s2: #89888d; transform-origin: -25% 230%; } &:nth-child(6) { --app-bg-s1: #1ac5fb; --app-bg-s2: #1d71f2; transform-origin: -125% 200%; } &:nth-child(7) { --app-bg-s1: #fe9b01; --app-bg-s2: #f67324; transform-origin: 175% 80%; } &:nth-child(8) { --app-bg-s1: #cb65f0; --app-bg-s2: #8628bb; transform-origin: 75% 100%; } &:nth-child(9) { --app-bg-s1: #1d71f2; --app-bg-s2: #1ac8fd; transform-origin: -25% 100%; } &:nth-child(10) { transform-origin: -125% 80%; } } .pallette { position: relative; z-index: 1; order: 1; display: flex; gap: 2em; margin-right: 2.25em; margin-bottom: -.25em; &:hover ~ .phone-con .screen { transition-delay: 0.5s; opacity: 0; } } .zoom-con { order: 1; } // -------------- // Theme picker // -------------- // Hide inputs input[type="radio"], input[type="checkbox"] { display: none; } .swatch { --swatch-size: max(30px,5em); --border-opacity: 0; background: white; width: var(--swatch-size); height: var(--swatch-size); border-radius: 50%; position: relative; cursor: pointer; border: max(1.5px, var(--border-width)) solid black; box-sizing: border-box; // transform: scale3d(0.85, 0.85, 1); background: radial-gradient(100% 100% at 50% 30%, var(--swatch-s1), var(--swatch-s2)); opacity: 0; transform: translate3d(0, 20%, 0) scale3d(0.75, 0.75, 1); animation: appear 0.333s var(--ease-out) forwards 0.75s; display: flex; align-items: center; color: white; justify-content: center; outline: none; &:before { content: ''; position: absolute; inset: -1px; border-radius: inherit; border: var(--border-width) solid black; background-image: radial-gradient(400% 300% at 50% 225%, transparent 20%, white); // filter: blur(0.2em); // box-shadow: inset 0 0 0.1em 0.1em black; } &:not([for="zoom"]) { &:after { content: ''; position: absolute; inset: calc(var(--border-width) * -1); border-width: inherit; border-style: inherit; opacity: var(--border-opacity); transition: 0.25s var(--ease-out); transition-property: opacity; border-radius: inherit; } &:hover { --border-opacity: 0.666; } &:focus { --border-opacity: 1; } } &:nth-child(1) { transform-origin: 100% 200%; } &:nth-child(2) { animation-delay: 0.8s; transform-origin: 80% 200%; } &:nth-child(3) { animation-delay: 0.85s; transform-origin: 50% 200%; } &:nth-child(4) { animation-delay: 0.9s; transform-origin: 40% 200%; } &:nth-child(5) { animation-delay: 0.95s; transform-origin: 20% 200%; } &[for="deep-purple"] { --swatch-s1: hsl(var(--deep-purple), 100%, 50%); --swatch-s2: hsl(calc(var(--deep-purple) - 60), 100%, 20%); &:before { opacity: 0.5; } } &[for="gold"] { --swatch-s1: hsl(var(--gold), 100%, 50%); --swatch-s2: hsl(var(--gold), 100%, 50%); } &[for="space-black"] { --swatch-s1: hsl(var(--space-black), 50%, 20%); --swatch-s2: hsl(var(--space-black), 50%, 10%); &:before { opacity: 0.5; } } &[for="silver"] { --swatch-s1: hsl(var(--silver), 50%, 80%); --swatch-s2: hsl(var(--silver), 50%, 10%); &:before { opacity: 0.75; } } &[for="random"] { &:before { background: none; } border-color: rgba(white, 0.25); transition: border-color 0.2s var(--ease-out); &:hover { border-color: rgba(white, 0.75); .icon { opacity: 1; } } .icon { position: absolute; inset: 22.5%; background: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m18 15 3 3m0 0-3 3m3-3h-4l-1-1-1-1h-1m4-13 3 3m0 0-3 3m3-3h-4l-1 1-1 1-6 8-1 1-1 1H3M3 6h4l1 1 1 1 1 1' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain; opacity: 0.75; transition: opacity 0.2s var(--ease-out); } #random:not(:checked) ~ .scene &:after { border: none; } #random:checked ~ .scene & { --swatch-s1: hsl(var(--r-h), var(--r-s), var(--r-l)); --swatch-s2: hsl(var(--r-h), var(--r-s), calc(min(98%, var(--r-l) + 10%))); &:after { border-color: hsl(var(--r-h), 50%, 75%); } .icon { background: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m18 15 3 3m0 0-3 3m3-3h-4l-1-1-1-1h-1m4-13 3 3m0 0-3 3m3-3h-4l-1 1-1 1-6 8-1 1-1 1H3M3 6h4l1 1 1 1 1 1' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain; } &:hover, &:focus { .tooltip { pointer-events: all; opacity: 1; } } } } &[for="zoom"] { --zoom-icon: url("data:image/svg+xml,%3Csvg width='32' height='32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m5 27 9-9m-9 9v-9m0 9h9M27 5l-9 9m9-9v9m0-9h-9' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); animation-delay: 1s; transform-origin: 40% 200%; border-color: rgba(white, 0.25); transition: border-color 0.2s var(--ease-out); &:before { background: none; } &:after { content: ''; position: absolute; inset: 22.5%; background: var(--zoom-icon) center / cover; opacity: 0.75; transition: opacity 0.2s var(--ease-out); } &:hover { border-color: rgba(white, 0.5); &:after { opacity: 1; } } &:focus { border-color: rgba(white, 0.75); &:after { opacity: 1; } } } } // Deep Purple theme .deep-purple { --c-h: var(--deep-purple); --g-hue-adjust: 40; --g-hue-adjust-2: 45; .section:last-of-type { --g-hue-adjust-2-y: 85%; } } // Gold theme .gold { --c-h: var(--gold); --g-hue-adjust: 0; .section:last-of-type { --g-hue-adjust: 15; --g-hue-adjust-2: -190; --g-lightness: 82.5%; } } // Space Black theme .space-black { --c-h: var(--space-black); --g-hue-adjust: 0; --g-lightness: 95%; .section:last-of-type { --g-hue-adjust: 215; --g-hue-adjust-2: -215; } } // Silver theme .silver { --c-h: var(--silver); --c-s: 10%; --g-hue-adjust: 40; --g-lightness: 95%; --g-hue-adjust-2-y: 85%; .section:last-of-type { --g-hue-adjust-2: 15; } .glow { opacity: 0.25; } } $themes: 'deep-purple', 'gold', 'space-black', 'silver', 'random'; @each $theme in $themes { label[for="#{$theme}"]:after { border-color: hsl(var(--#{$theme}), 50%, 75%); } ##{$theme}:checked { ~ .scene .phone .bg .#{$theme} { opacity: 1; z-index: 1; transition-delay: 0s; .section:before { transition-delay: 0s; transform: scale3d(1,1,1) translate3d(0, 0 ,0); } } ~ .scene .pallette label[for="#{$theme}"] { --border-opacity: 1 !important; } } } // Zoom #zoom:checked ~ .scene { --size: max(5px, 2vmin); opacity: 0; animation: appear 0.5s var(--ease-out) forwards; .phone { order: 1; } .pallette, .zoom-con { order: 0; font-size: 50%; } label[for="zoom"] { --zoom-icon: url("data:image/svg+xml,%3Csvg width='32' height='32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m14 18-9 9m9-9v9m0-9H5M18 14l9-9m-9 9V5m0 9h9' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } } #zoom:not(:checked) ~ .scene { opacity: 0; animation: appear-again 0.5s var(--ease-out) forwards; @keyframes appear-again { to { transform: scale3d(1,1,1); opacity: 1; } } } // Random gradient .random { background: hsl(var(--r-h), var(--r-s), var(--r-l)); &--colourful { --bm-outer: overlay; --bm-inner: difference; } &--moody { --bm-outer: exclusion; --bm-inner: multiply; } &--neon { --bm-outer: hue; --bm-inner: multiply; } &--abstract { --bm-outer: color-burn; --bm-inner: difference; } &--grayscale { --bm-outer: color-burn; --bm-inner: difference; filter: brightness(1.2) grayscale(100%) contrast(2); } &--light-leak { --bm-outer: color-burn; --bm-inner: multiply; } } .shapes { mix-blend-mode: var(--bm-outer, overlay); background: hsl(var(--r-h), var(--r-s), 85%); position: absolute; inset: 0%; z-index: 1; backface-visibility: hidden; } .shape { position: absolute; pointer-events: none; transition-property: background, inset, border-radius, filter, transform; will-change: inset, border-radius, filter; mix-blend-mode: var(--bm-inner, difference); top: 50%; left: 50%; aspect-ratio: 1/1; background: hsl(var(--r-h), var(--r-s), var(--r-l)); width: var(--w, 99%); border-radius: var(--b-r, 55%); filter: blur(var(--b)); transform: translate3d(calc(var(--x, -1%) - 50%), calc(var(--y, -8%) - 50%), 0) scale3d(var(--s-x, 1.9), var(--s-y, 1.15), 1) rotate(var(--r, 260deg)); } .tooltip { --t-caret: 0.5em; position: absolute; z-index: 1; top: calc(100% + var(--t-caret) + 0.5em); white-space: nowrap; background: black; color: rgba(white, 0.75); padding: 0.25em; font-size: max(11px, 1em); border-radius: 0.25em; border: 1px solid lighten(black, 25); pointer-events: none; opacity: 0; transition: opacity 0.25s var(--ease); &:after { content: ''; position: absolute; bottom: calc(100% - calc(var(--t-caret) / 2) - 1px); left: calc(50% - calc(var(--t-caret) / 2)); border-radius: inherit; height: var(--t-caret); width: var(--t-caret); background: inherit; border: inherit; border-top-color: transparent; border-left-color: transparent; transform: rotate(-135deg); } #zoom:checked ~ .scene & { top: unset; bottom: calc(100% + var(--t-caret) + 0.5em); &:after { transform: rotate(45deg); top: calc(100% - calc(var(--t-caret) / 2) - 1px); bottom: unset; } } }
js部分
// Tiny bit of JS to ensure that the notch doesn't move about when you resize the screen const delay = 300; let afterResize; let currentStyle; window.onresize = function(){ document.body.classList.add('is-resizing'); clearTimeout(afterResize); afterResize = setTimeout(() => document.body.classList.remove('is-resizing'), delay); }; document.getElementById('zoom').addEventListener('click', () => { document.body.classList.add('is-resizing'); setTimeout(() => document.body.classList.remove('is-resizing'), delay) }); // Generating random gradient let dimension = 1000; // Size of tile to be download px const styles = ['colourful', 'moody', 'neon', 'abstract', 'grayscale', 'light-leak']; const generateBtn = document.querySelector('[for="random"]'); const canvas = document.querySelector('.canvas'); const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); // Generate randomised gradients generateBtn.addEventListener('click', generateOrSave); function generateOrSave(e) { if (e.metaKey) { saveGradient(e); } else { generateGradient(); } } function generateGradient() { // Assign style const newStyle = styles[Math.floor(Math.random() * styles.length)]; // console.log(styles, styles[Math.floor(Math.random() * styles.length)]); if (currentStyle) canvas.classList.remove(`random--${currentStyle}`); currentStyle = newStyle; canvas.classList.add(`random--${newStyle}`); // Loop through each canvas and assign a bunch of random CSS variables const shapes = canvas.getElementsByClassName('shape'); document.body.style.setProperty('--r-h', `${random(0, 360)}deg`); document.body.style.setProperty('--r-s', `${random(40, 90)}%`); document.body.style.setProperty('--r-l', `${random(55, 90)}%`); Object.values(shapes).forEach((shape) => { shape.style.setProperty('--r-h', `${random(0, 360)}deg`); shape.style.setProperty('--r-s', `${random(40, 90)}%`); shape.style.setProperty('--r-l', `${random(55, 90)}%`); shape.style.setProperty('--w', `${random(0, 30) + 85}%`); shape.style.setProperty('--b-r', `${random(20, 60)}%`); shape.style.setProperty('--b', `${random(5, 75) / 10}em`); shape.style.setProperty('--x', `${random(0, 100) - 50}%`); shape.style.setProperty('--y', `${random(0, 100) - 50}%`); shape.style.setProperty('--s-x', `${1 + ((random(0, 130) - 30) / 100)}`); shape.style.setProperty('--s-y', `${1 + ((random(0, 130) - 30) / 100)}`); shape.style.setProperty('--r', `${random(0, 720) - 360}deg`); }) } // Convert RGB colour to Hex // Needed for api.color.pizza call const rgba2hex = (rgba) => `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}` // Save gradient function saveGradient(e) { const gradient = canvas; const rect = gradient.getBoundingClientRect(); const scale = dimension / rect.width // Get canvas background color const color = rgba2hex(window.getComputedStyle(gradient, null).getPropertyValue('background-color')); console.log(color, scale, rect); // Get name of color for use in file name fetch(`https://api.color.pizza/v1/${color.substring(1)}`) .then(c => c.json()) .then(c => { // console.log(c); // Convert DOM to canvas domtoimage.toPng(gradient, { bgColor: '#ffffff', width: rect.width * scale, height: rect.height * scale, style: { 'transform': `scale(${scale})`, 'transform-origin': 'top left' } }) // Download image .then(function (dataUrl) { // const img = new Image(); // img.src = dataUrl; // document.body.appendChild(img); // Render canvas as a link and click dat const link = document.createElement('a'); link.download = `${currentStyle}-${c['paletteTitle'].toLowerCase().replaceAll(' ','-')}-gradient`; link.href = dataUrl; link.click(); }) }); }