一、前言
灵动岛(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 问世五年来的第一个重大操作变化。
我主要实现的是从灵动岛跳转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();
})
});
}