📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
---
|
||||
name: 3d-ui
|
||||
description: Web and App implementation guide for 3D UI. Trigger when user wants actual 3D objects, perspective effects, and spatial depth.
|
||||
date_added: "2026-06-17"
|
||||
risk: safe
|
||||
source: self
|
||||
source_type: self
|
||||
---
|
||||
|
||||
# 3D UI
|
||||
|
||||
> "Breaking the plane. Interfaces that exist in a three-dimensional, rotatable space."
|
||||
|
||||
|
||||
## When to Use
|
||||
Use this sub-style when the user's request matches the aesthetic described above. This is a child reference of the `design-it` skill and is not meant to be triggered directly.
|
||||
|
||||
## Core Principles
|
||||
1. **True Depth (Z-Axis Translation)**: Elements don't just have shadows; they physically move closer to or further from the camera.
|
||||
2. **Perspective**: Use CSS perspective or WebGL to create realistic vanishing points.
|
||||
3. **Interactive Rotation**: Elements should respond to mouse movement or device gyroscope by tilting or rotating in 3D space.
|
||||
|
||||
## Visual DNA
|
||||
- **Colors**: Bold, striking palettes like **Midnight Luxury** or **Industrial Chic**. 3D elements need high contrast to show their geometry.
|
||||
- **Typography**: Bold, blocky, or extruded text.
|
||||
- **Graphics**: Instead of flat icons, use rendered 3D assets (.glb, .gltf, or high-res PNGs of 3D objects).
|
||||
|
||||
## Web Implementation
|
||||
- Rely heavily on `perspective`, `transform-style: preserve-3d`, and `rotateX`/`rotateY`.
|
||||
- **CSS Example**:
|
||||
```css
|
||||
.perspective-container {
|
||||
perspective: 1000px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-3d {
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform 0.5s ease;
|
||||
|
||||
/* Initial slight rotation */
|
||||
transform: rotateX(15deg) rotateY(-15deg);
|
||||
}
|
||||
|
||||
.card-3d:hover {
|
||||
/* Straighten out on hover */
|
||||
transform: rotateX(0) rotateY(0) translateZ(50px);
|
||||
}
|
||||
|
||||
/* Inner elements popping out */
|
||||
.card-content {
|
||||
transform: translateZ(30px); /* Pushes content 30px closer to viewer */
|
||||
}
|
||||
```
|
||||
|
||||
## App Implementation
|
||||
|
||||
### SwiftUI
|
||||
```swift
|
||||
struct Card3D: View {
|
||||
@State private var dragOffset = CGSize.zero
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("3D Card")
|
||||
.font(.largeTitle.bold())
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.frame(width: 300, height: 400)
|
||||
.background(
|
||||
LinearGradient(colors: [.blue, .purple], startPoint: .topLeading, endPoint: .bottomTrailing)
|
||||
)
|
||||
.cornerRadius(24)
|
||||
.shadow(radius: 20)
|
||||
// Magic 3D effect based on drag gesture
|
||||
.rotation3DEffect(
|
||||
.degrees(Double(dragOffset.width / 10)),
|
||||
axis: (x: 0, y: 1, z: 0),
|
||||
perspective: 0.5
|
||||
)
|
||||
.rotation3DEffect(
|
||||
.degrees(Double(-dragOffset.height / 10)),
|
||||
axis: (x: 1, y: 0, z: 0),
|
||||
perspective: 0.5
|
||||
)
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.onChanged { value in
|
||||
withAnimation(.interactiveSpring()) {
|
||||
dragOffset = value.translation
|
||||
}
|
||||
}
|
||||
.onEnded { _ in
|
||||
withAnimation(.spring()) {
|
||||
dragOffset = .zero
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
- SwiftUI makes this incredibly easy with `.rotation3DEffect()`.
|
||||
- Use the `perspective` parameter (default 1/6, higher = more distorted) to control the camera distance.
|
||||
- Link the rotation axes (`x`, `y`) to drag gestures or CoreMotion (gyroscope) for interactive 3D UI.
|
||||
|
||||
### Flutter
|
||||
```dart
|
||||
class Card3D extends StatefulWidget {
|
||||
@override
|
||||
State<Card3D> createState() => _Card3DState();
|
||||
}
|
||||
|
||||
class _Card3DState extends State<Card3D> {
|
||||
Offset _offset = Offset.zero;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onPanUpdate: (details) {
|
||||
setState(() => _offset += details.delta);
|
||||
},
|
||||
onPanEnd: (_) {
|
||||
setState(() => _offset = Offset.zero); // Snap back
|
||||
},
|
||||
child: TweenAnimationBuilder(
|
||||
tween: Tween<Offset>(begin: Offset.zero, end: _offset),
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
builder: (context, Offset offset, child) {
|
||||
// Perspective Matrix
|
||||
final transform = Matrix4.identity()
|
||||
..setEntry(3, 2, 0.001) // perspective
|
||||
..rotateX(-offset.dy * 0.01)
|
||||
..rotateY(offset.dx * 0.01);
|
||||
|
||||
return Transform(
|
||||
transform: transform,
|
||||
alignment: FractionalOffset.center,
|
||||
child: Container(
|
||||
width: 300,
|
||||
height: 400,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(colors: [Colors.blue, Colors.purple]),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: const [BoxShadow(color: Colors.black45, blurRadius: 20)],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Text('3D Card',
|
||||
style: TextStyle(color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
- The secret to perspective in Flutter is `Matrix4.identity()..setEntry(3, 2, 0.001)`.
|
||||
- Wrap the target container in a `Transform` widget and apply rotations on the X and Y axes.
|
||||
- Use `TweenAnimationBuilder` to smooth out the return-to-center physics.
|
||||
|
||||
### React Native
|
||||
```jsx
|
||||
const Card3D = () => {
|
||||
const pan = useRef(new Animated.ValueXY()).current;
|
||||
|
||||
const panResponder = useRef(
|
||||
PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => true,
|
||||
onPanResponderMove: Animated.event([null, { dx: pan.x, dy: pan.y }], { useNativeDriver: false }),
|
||||
onPanResponderRelease: () => {
|
||||
Animated.spring(pan, { toValue: { x: 0, y: 0 }, useNativeDriver: false }).start();
|
||||
},
|
||||
})
|
||||
).current;
|
||||
|
||||
// Map drag distance to degrees
|
||||
const rotateX = pan.y.interpolate({ inputRange: [-200, 200], outputRange: ['20deg', '-20deg'] });
|
||||
const rotateY = pan.x.interpolate({ inputRange: [-200, 200], outputRange: ['-20deg', '20deg'] });
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
{...panResponder.panHandlers}
|
||||
style={{
|
||||
width: 300, height: 400,
|
||||
backgroundColor: '#6b21a8',
|
||||
borderRadius: 24,
|
||||
justifyContent: 'center', alignItems: 'center',
|
||||
// Pseudo-3D transforms
|
||||
transform: [
|
||||
{ perspective: 1000 },
|
||||
{ rotateX },
|
||||
{ rotateY }
|
||||
]
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: '#fff', fontSize: 32, fontWeight: '700' }}>3D Card</Text>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
```
|
||||
- True 3D models require `react-three-fiber`.
|
||||
- For UI perspective transforms, use the `transform` array: `[{ perspective: 1000 }, { rotateX: '...' }, { rotateY: '...' }]`.
|
||||
- `perspective` MUST be the first item in the transform array for the effect to render correctly.
|
||||
|
||||
### Jetpack Compose
|
||||
```kotlin
|
||||
@Composable
|
||||
fun Card3D() {
|
||||
var offset by remember { mutableStateOf(Offset.Zero) }
|
||||
val animatedOffset by animateOffsetAsState(
|
||||
targetValue = offset,
|
||||
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(300.dp, 400.dp)
|
||||
.pointerInput(Unit) {
|
||||
detectDragGestures(
|
||||
onDrag = { change, dragAmount ->
|
||||
change.consume()
|
||||
offset += dragAmount
|
||||
},
|
||||
onDragEnd = { offset = Offset.Zero },
|
||||
onDragCancel = { offset = Offset.Zero }
|
||||
)
|
||||
}
|
||||
.graphicsLayer {
|
||||
// Apply 3D rotation based on drag offset
|
||||
rotationX = -animatedOffset.y * 0.1f
|
||||
rotationY = animatedOffset.x * 0.1f
|
||||
cameraDistance = 8f * density // Sets the perspective
|
||||
}
|
||||
.shadow(20.dp, RoundedCornerShape(24.dp))
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(Brush.linearGradient(listOf(Color.Blue, Color.Magenta)))
|
||||
) {
|
||||
Text("3D Card",
|
||||
color = Color.White, fontSize = 32.sp, fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
}
|
||||
```
|
||||
- Apply 3D transformations inside `Modifier.graphicsLayer { }`.
|
||||
- Set `rotationX` and `rotationY` for the tilt.
|
||||
- **Critical**: Set `cameraDistance` to establish the Z-axis perspective vanishing point. Usually `8f * density` is a good starting point.
|
||||
|
||||
## Do's and Don'ts
|
||||
- **DO**: Tie 3D rotation to user input (mouse move on web, device tilt on mobile) for maximum impact.
|
||||
- **DON'T**: Make text itself 3D unless it's a massive headline. 3D text is generally unreadable at small sizes.
|
||||
|
||||
## Limitations
|
||||
- This is a styling reference and does not replace environment-specific validation, accessibility testing, or expert review.
|
||||
- Ensure appropriate contrast ratios and responsive behaviors are verified separately.
|
||||
Reference in New Issue
Block a user