正在加载视频...

视频加载失败

CSS Tip! 💪 You can create these tab controls with CSS :has() + radio buttons ✨ .tabs:has(input:nth-of-type(3)) { --count: 3; } .tabs:has(:checked:nth-of-type(3)) { --active: 2; } .tabs::after { translate: calc(var(--active, 0) * 100%) 0; width: calc(100% / var(--count)); } Two CSS :has() tricks here combined with a rendering trick...

437,487 次观看 • 2 年前 •via X (Twitter)

9 条评论

jhey ▲🐻🎈 的头像
jhey ▲🐻🎈2 年前

Here's that @CodePen link! 🚀 Remember, CSS :has() is available in all the major browsers now 👀 It's a great time to start getting familiar with it ⭐️

toprakcavusdag.js 的头像
toprakcavusdag.js2 年前

That's super. Thank you for sharing us

jhey ▲🐻🎈 的头像
jhey ▲🐻🎈2 年前

More than welcome! 🙏

Kay 🇮🇪 的头像
Kay 🇮🇪2 年前

Tried to find it for tailwind but no luck!?

Carlito 的头像
Carlito2 年前

Sorcery!

JEM 的头像
JEM2 年前

still wrapping my head around :has() 😵‍💫

jhey ▲🐻🎈 的头像
jhey ▲🐻🎈2 年前

Wrote this one in 2022 that might help 🤙 Got some much cooler demos to show off for it now though 😁

randomhuman 的头像
randomhuman2 年前

Beautiful 👌

Souvik Bhattacharjee 的头像
Souvik Bhattacharjee2 年前

Nice 🔥

相关视频

CSS Trick 🤙 You can create these tab bar controls by using :has() to count the number of tabs ⭐️ .tabs:has(input:nth-of-type(3)){--count: 3;} .tabs:has(:checked:nth-of-type(3)){--active: 200%;} .tabs::after{ translate:var(--active) 0;} Let's break it down in this ! 📼 Couple of CSS :has() tricks here combined with custom properties 😎 First things first, lay out the tabs using display: grid. This gives you a way to create equal-width tabs 🙏 .tabs { display: grid; grid-auto-flow: column; } Then you use :has() to count the number of tabs and store that in a custom property 🤓 .tabs:has(input:nth-of-type(3)) { --count: 3; } .tabs:has(input:nth-of-type(4)) { --count: 4; } Using the cascade, the last valid :has() gives you the number of tabs 🫶 Using the tab count, you can size the tab indicator. For the tab indicator, use the tabs pseudoelement: .tabs::after { content: ""; position: absolute; height: 100%; width: calc(100% / var(--count)); } See how you can use --count to determine its size 📏 Next, use :has() to determine which tab is active or :checked with input [type=radio] .tabs:has(:checked:nth-of-type(2)) { --active: 1; } .tabs:has(:checked:nth-of-type(3)) { --active: 2; } You can use a zero-indexed translation here. If the second input is :checked, set --active: 1, then translate the pseudoelement on the tabs to that position 👉 .tabs::after { translate: calc(var(--active, 0) * 100%) 0; } Or you could set active to the translation: .tabs:has(:checked:nth-of-type(2)) { --active: 100%; } Setting the custom property allows you to use the index elsewhere if you need it 🤙 The final piece is using mix-blend-mode 👀 The tabs have a black background-color, the pseudoelement is white, and the label text is white. When you use mix-blend-mode: difference on the pseudoelement it will give this effect that the text transitions from white to black sliding across 😎 .tabs::after { color: hsl(0 0% 100%); mix-blend-mode: difference; } You can totally mix up the colors here though and go with a different effect. The mechanics of how you can use CSS :has() is the main point here 🙏 As always, any questions, suggestions, etc. let me know CodePen.IO link below! 👇 (There's even a Tailwind CSS play for this one too 👀)

jhey ʕ•ᴥ•ʔ

70,730 次观看 • 2 年前

CSS Tip! 🎠 You can create a responsive infinite marquee animation with container queries and no duplicate items 🤙 li{ animation: slide; } @​keyframes slide { to { translate: 0% calc(var(--i) * -100%);}} The trick is animating the items, not the list 😎 More tricks 👇 To get this one working, you need to animate the items and not the list (Watch the video first?). Each item needs to know its row index (--i) in the list and the parent needs to know how many rows are in the list: ul { --count: 12; } li:nth-of-type(1), li:nth-of-type(2) { --i: 0; } li:nth-of-type(3), li:nth-of-type(4) { --i: 1; } Once you have that, translate each item based on its row index in the list li { translate: 0% calc((var(--count) - var(--i)) * 100%); } Now for the animation. The key here is that each row has an animation-delay calculated from its index (--i). That number is offset to make it negative so the animation start is offset ✨ ul { --duration: 10s; } li { --delay: calc((var(--duration) / var(--count)) * (var(--i) - 8)); animation: slide var(--duration) var(--delay) infinite linear; } Make sure to wrap that animation in: @​media (prefers-reduced-motion: no-preference) { ... } Lastly, the fun parts! 🤓 To create the "vignette" mask. Use a layered mask on the container 😷 .scene { --buff: 3rem; height: 100%; width: 100%; mask: linear-gradient(transparent, white var(--buff) calc(100% - var(--buff)), transparent), linear-gradient(90deg, transparent, white var(--buff) calc(100% - var(--buff)), transparent); mask-composite: intersect; } To create the 3D skewed effect, use a chained transform (Try toggling it in the demo ⚡️): .grid { transform: rotateX(20deg) rotateZ(-20deg) skewX(20deg); } As for the responsive part, use container queries! 🔥 article { container-type: inline-size; } When the article (card) is narrower than 400px update the grid and animation settings 🤙 Double the rows means double the duration! @​container (width < 400px) { .grid { --count: 12; grid-template-columns: 1fr; } li:nth-of-type(1) { --i: 0; } li:nth-of-type(2) { --i: 1; } li:nth-of-type(3) { --i: 2; } li:nth-of-type(4) { --i: 3; } li { --duration: 20s; } } CSS has the magic to be able to update those animations at runtime based on your custom property values 😎 An added bonus in this demo is that it doesn't require any JavaScript at all, for any of it 🤯 We can use CSS :has() for those toggles that update the styles, even the theme toggle! 🫶 Any questions, let me know! Make sure to check out the video. Will do a walkthrough one to follow-up 🤙 CodePen.IO link below! 👇

jhey ʕ•ᴥ•ʔ

542,159 次观看 • 2 年前

CSS Tip! 💫 You can create this responsive perspective warp animation with 3D CSS and container queries ✨ (Video reveals trick 👀) .warp { container-type: size; perspective: 100px; transform-style: preserve-3d; resize: both; overflow: hidden; } Couple of tricks in this one 🤓 The main idea is to create a tunnel (an open-ended cube). On each side of the tunnel, use linear-gradient to create the grid lines ✨ .side { background: linear-gradient(#​fff 0 1px, transparent 1px 5%) 50% 0 / 5% 5%, linear-gradient(90deg, #​fff 0 1px, transparent 1px 5%) 50% 50% / 5% 5%; } To position each side, you rotate on the x-axis by 90deg. Each side would become invisible at this point. So you give the scene perspective 😉 .warp__side--top { width: 100cqi; height: 100cqmax; transform-origin: 50% 0%; transform: rotateX(-90deg); } The cool part here is that you want to make each side the same height. But the container is responsive. So you can use a container query and make sure each side is 100cqmax tall 🫶 Then the "beams". Each side contains "beams". They have different colors, sizes, and positions, and move at different speeds ⚡️ We can control that through scoped custom properties. .beam { width: 5%; position: absolute; top: 0; left: calc(var(--x, 0) * 5%); aspect-ratio: 1 / 2; background: linear-gradient( hsl(var(--hue) 80% 60%), transparent ); translate: 0 100%; animation: warp calc(var(--speed, 0) * 1s) calc(var(--delay, 0) * -1s) infinite linear; } The magic here is though that a beam's animation is as basic as translating it from the top of the side to the bottom. And you can get that distance with a container query again 🔥 @​keyframes warp { 0% { translate: -50% 100cqmax; } 100% { translate: -50% -100%; } } And that is pretty much it! A cool warp animation effect using 3D CSS and container queries ⚡️ If you have any questions, let me know ᵔᴥᵔ CodePen.IO link below! 👇

jhey ʕ•ᴥ•ʔ

187,474 次观看 • 2 年前

CSS Tip! ✨ You can create these parallax effects and image cross-fades with scroll-driven animations 🤙 img { animation: fade; animation-timeline: view(); mix-blend-mode: plus-lighter } img:last-of-type { animation-direction: reverse; } @​keyframes fade { to { opacity: 0; }} This one's fun! 😁 The trick with the cross-fading image is to make use of one animation that runs at the same time on two images inside a container. You use the same animation, animation-timeline, and animation-range. But, you use animation-direction: reverse on one of the images so they go in the opposite direction 🫶 The use of mix-blend-mode: plus-lighter; produces a better cross-fade result 💯 A viewTimeline (view()) works because you know that both images are the same height. The range you can use is img { animation-timeline: view(); animation-range: cover 45% cover 55%; } That means when the image has covered 45% of the scrollport (In this case, the window), start the animation. And finish when it has covered 55% 🎬 How about the slight parallax? This is a trick with calc(). You know the top of the small image and the big image line up. And you can do this by absolutely placing the caption outside of the small image. The trick is to translate the small image by a distance so it lines up with the bottom of the big image. You can do that like this :root { --catch-up: calc( var(--big-height) - var(--small-height) ); } @​keyframes move { to { translate: 0 var(--catch-up); }} Then drive that animation with a scroll-driven animation using the container of both images as the driver 🤙 /* section contains both images */ section { view-timeline: --container; } .img-fader { animation: catch-up both linear; animation-timeline: --container; animation-range: 50vh calc(100vh + (var(--big-height) * 0.25)); } That's it! Scroll-driven image cross-fading and parallax effects without any JavaScript. This demo will work in all browsers as there is some JavaScript in place where the API isn't supported 🤙 To do that, it uses GSAP ScrollTrigger 🏆 As always, any questions, requests, etc. hit me up! 🤙 CodePen.IO link below 👇

jhey ʕ•ᴥ•ʔ

242,074 次观看 • 2 年前

CSS Tip! 🚥 You can create these trending expanding scroll indicators with scroll-driven animations and flex 🤙 .indicator { animation: grow; animation-range: contain calc(50% - var(--size)...; animation-timeline: var(--card); } @​keyframes grow { 50% { flex: 3; }} What's the trick? Put the indicators in a container using flex layout and set a width larger than the number of indicators 😉 .indicators { aspect-ratio: 7 / 1; display: flex; } Importantly, set no gap 🤏 To mimic the gap set a transparent border on each indicator and set the background using padding-box .indicator { background: linear-gradient(#​fff, #​fff) padding-box; border-radius: 50px; border: 4px solid transparent; } Now for the animation. You want to create a view-timeline for each card that moves across 🤙 li:nth-of-type(1) { view-timeline: --one inline; } li:nth-of-type(2) { view-timeline: --two inline; } Make sure they use the inline axis too! The trick is hoisting these view-timeline so the indicators can use them with timeline-scope 👀 .track { timeline-scope: --one, --two, ...; } All that's left is for you to create the animation piece using some calc with the card size ⚡️ .indicator { --size: calc(var(--card-width) * 0.9); animation: grow both linear; animation-range: contain calc(50% - var(--size)) contain calc(50% + var(--size)); } .indicator:nth-of-type(1) { animation-timeline: --one; } .indicator:nth-of-type(2) { animation-timeline: --two; } @​keyframes grow { 50% { flex: 3; }} And there you have it, responsive scroll indicators using CSS scroll-driven animations 😎 Sprinkle a little JavaScript to make them clickable and scroll the the right card ✨ const shift = (event) => { if (event​.target.tagName === "BUTTON") { const index = [...event.target.parentNode.children].indexOf(event​.target); const item = document.querySelector(`li:nth-of-type(${index + 1})`); item.scrollIntoView({ behavior: "smooth", inline: "center" }); } }; As always, any questions or suggestions, let me know. I've put a JavaScript fallback in to use GSAP in browsers that don't have scroll-driven animations 🫶 CodePen.IO link below! 👇

jhey ʕ•ᴥ•ʔ

575,538 次观看 • 2 年前

CSS Tip! 🤯 You can create a CSS-only version of this balance slider using a scroll animation on the underlying input[type=range] 🚀 ::-webkit-slider-thumb { view-timeline: --thumb inline; } Scroll animation driven by the slider thumb animates a number between the "min" and "max" of the range 😅 @​property --value { inherits: true; initial-value: 0; syntax: ' '; } @​keyframes sync { to { --value: 100; }} Tie that up to the contain animation-range ⚡️ .control { animation: sync both linear reverse; animation-timeline: --thumb; animation-range: contain; } Hoist the view timeline so all the parts of the control can use it! .control { timeline-scope: --thumb; } Use that value in a counter which is used for the labels. Create a low and a high for each side 😇 .control__label { counter-reset: low var(--value) high calc(100 - var(--value)); } .control__label::before { content: "COFFEE " counter(low) "%"; } .control__label::after { content: counter(high) "% MILK"; } That's the magic of updating the label values ✨ For the big track, it's a fake track. You can make use of the same --value property and do some calc() to work out the width of each part. .control__track::before { width: calc(var(--value) * 1% - 0.5rem); background: var(--coffee); border-radius: 4px; transition: width 0.1s; } The width leaves a little gap for the indicator piece 🤙 The color calculation for --coffee isn't too wild but again you can use the same --value .control__track { --coffee: hsl(24 74% calc( 24% + (30% * ((100 - var(--value, 0)) / 100)) / 1 ) / 0.4); } Now for the last piece. Making the track change height. You could set up another custom property and animate its value using the --thumb timeline too 🔥 @​property --shift { initial-value: 0; inherits: true; syntax: ' '; } @​keyframes shift { 0%, 31%, 61%, 100% { --shift: 0; } 32%, 60% { --shift: 1; } } Then use that --shift to update the translation of the label and height of the track 🤓 .label { transform: translateY(calc(var(--shift) * 50%)); transition: transform var(--speed) var(--timing); } Cool part here is that you can use the control to work out the @​keyframes percentages 😅 Oh. And the timing for that little bounce? Use the linear() function 😎 :root { --timing: linear( 0, 0.5007 7.21%, 0.7803 12.29%, 0.8883 14.93%, 0.9724 17.63%, 1.0343 20.44%, 1.0754 23.44%, 1.0898 25.22%, 1.0984 27.11%, 1.1014 29.15%, 1.0989 31.4%, 1.0854 35.23%, 1.0196 48.86%, 1.0043 54.06%, 0.9956 59.6%, 0.9925 68.11%, 1 ); } Should probably do a video on this one. Lots of little custom property tricks for sure! 💯 It's not too far off the range slider with the tooltip that came up previously As always, any questions, let me know! Also, this one only works in Chrome currently ✅🥲 This one's a bit rocket science ha 🚀 CodePen.IO link below! 👇

jhey ʕ•ᴥ•ʔ

377,586 次观看 • 2 年前

CSS Trick 🧲 You can create magnetic links with the power of custom properties and some JavaScript 💪 a { translate: calc(clamp(-1, var(--x), 1) * var(--pad-x)) ...; transition: translate var(--s, 1s) var(--ease, var(--elastic)); } a:hover { --s: 0s; } The trick here is to pad out the list items wrapping your links and use that as a translation limit 🛑 Start by using some JavaScript to calculate a value between -1 and 1 for both the x/y axis on pointermove for each list item, not the link! 🔗 If your pointer was at the center of the item, you'd get [0,0]. If it was in the top right, you'd get [1,-1] ☝️ It's worth checking out the JavaScript snippet to see how the mapping function works. Essentially, you create a function that when given a value between two bounds, will give you a mapped value back 🤙 const mapX = mapRange( item.offsetWidth * -0.5, item.offsetWidth * 0.5, 1, -1 ) Then, on pointermove, you plug the pointer position in to get the value back out and pass that into your CSS const x = mapX(item.centerX - event.x) document​.documentElement​.style.setProperty(--x, x) When the pointer leaves the list item, you make sure to reset these values back to 0 ✨ Once CSS has your values, it's the trick of updating the translation of each part You know that in each axis, you only want to translate the link by the padding amount li a { translate: calc(clamp(-1, var(--x), 1) * var(--pad-x)) calc(clamp(-1, var(--y), 1) * var(--pad-y)); transition: translate var(--speed, 1s) var(--ease, var(--elastic)); } This will translate the link within the list item by the desired amount. The cool part here is that you can set an offset for the text inside the link and have that move at a different rate ⭐️ By only updating the --pad-x/y custom properties for the inside the link, you can control how much it moves nav a span { --pad-x: 0.25rem; --pad-y: 0.25rem; } And the last piece, how do you update the behavior for transition speeds? And so it springs back like that? Again, use custom properties ✨ a:hover { --s: 0s; } a { transition: translate var(--s, 1s) var(--ease, var(--elastic)); } By default, a link will use --elastic easing via linear() and have a transition-duration of 1s. When a link is hovered that speed becomes 0s because you want the link to magnetise to your pointer. How about that little gap between when your pointer enters the item but hasn't hovered the link? Set a different transition so it transitions to being hovered 🫶 nav li:hover a { --ease: ease-out; --speed: 0.1s; } That's kinda it! 🙌 Use JavaScript (~40 loc) to get the information and then let CSS do all the lifting for you 💪 Any questions or suggestions, let me know 🙏 If you want a walkthrough video, also let me know please 🙏 CodePen.IO link below 👇

jhey ʕ•ᴥ•ʔ

164,863 次观看 • 2 年前

CSS Tip! ✨ It's 2024 and you have a new way to make animated borders 🚀 .glow::after { offset-path: rect(0 100% 100% 0 round var(--radius)); animation: loop; } @​keyframes loop { to { offset-distance: 100%; }} Using the offset-* properties you can animate elements along the perimeter of others 😍 The rect() value gained support in Safari 17.2 🙌 To start, you create an element and put it inside your main element. For example, you put a span inside the button 🤙 Click me! Make the element fill its parent with absolute positioning and inset [data-glow] { position: absolute; inset: 0; } Now the good part, you use a pseudoelement on that element and define an offset-path [data-glow]::after { content: ""; offset-path: rect(0 auto auto 0 round var(--radius)); animation: loop 2.6s infinite linear; } With the rect value, you are saying the path fills the parent container: top: 0 right: auto || 100% bottom: auto || 100% left: 0 Then you can use round to make sure the path uses the same radius as whatever the parent has The @​keyframes animation merely animates the offset-distance of that pseudoelement to 100% @​keyframes loop { to { offset-distance: 100%; }} You can see this more clearly in the video 🫶 The offset-* properties also include an offset-anchor property. This allows you to dictate which point of the element follows the path. For example: anchor-offset: 100% 50%; This means that the "right, center" of the element will follow the perimeter of the parent element 🤙 Lastly, the visuals 🎨 For color, you can use a gradient such as a linear gradient to fill the pseudo-element. [data-glow]::after { background: radial-gradient( circle at right, hsl(320 90% 100%), transparent 50% ); } Then clip away everything so you only have the border and can still have translucent backgrounds, etc. Use a mask with mask-composite ✨ A little transparent border trick: [data-glow] { border: 2px solid transparent; mask: linear-gradient(transparent, transparent), linear-gradient(white, white); mask-clip: padding-box, border-box; mask-composite: intersect; } Bit of a long one. Hope you find it useful 🙏 CodePen.IO link below 👇

jhey ʕ•ᴥ•ʔ

283,498 次观看 • 2 年前