android_frame.jsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /**
  2. * AndroidFrame — Android设备边框(参考Pixel 8系列)
  3. *
  4. * 含:punch-hole相机 + 状态栏 + 导航栏 + 圆角
  5. *
  6. * 用法:
  7. * <AndroidFrame time="9:41" battery={85}>
  8. * <YourAppContent />
  9. * </AndroidFrame>
  10. */
  11. const androidFrameStyles = {
  12. wrapper: {
  13. display: 'inline-block',
  14. padding: 10,
  15. background: '#1a1a1a',
  16. borderRadius: 44,
  17. boxShadow: '0 0 0 2px #2a2a2a, 0 20px 60px rgba(0,0,0,0.3)',
  18. position: 'relative',
  19. },
  20. screen: {
  21. position: 'relative',
  22. borderRadius: 36,
  23. overflow: 'hidden',
  24. background: '#fff',
  25. },
  26. statusBar: {
  27. position: 'absolute',
  28. top: 0,
  29. left: 0,
  30. right: 0,
  31. height: 32,
  32. display: 'flex',
  33. alignItems: 'center',
  34. justifyContent: 'space-between',
  35. padding: '0 24px',
  36. fontSize: 14,
  37. fontWeight: 500,
  38. fontFamily: 'Roboto, -apple-system, sans-serif',
  39. zIndex: 20,
  40. pointerEvents: 'none',
  41. },
  42. punchHole: {
  43. position: 'absolute',
  44. top: 10,
  45. left: '50%',
  46. transform: 'translateX(-50%)',
  47. width: 14,
  48. height: 14,
  49. background: '#000',
  50. borderRadius: '50%',
  51. zIndex: 30,
  52. },
  53. statusIcons: {
  54. display: 'flex',
  55. alignItems: 'center',
  56. gap: 6,
  57. },
  58. batteryText: {
  59. fontSize: 11,
  60. fontWeight: 600,
  61. marginLeft: 2,
  62. },
  63. content: {
  64. position: 'absolute',
  65. top: 32,
  66. left: 0,
  67. right: 0,
  68. bottom: 24,
  69. overflow: 'auto',
  70. },
  71. navBar: {
  72. position: 'absolute',
  73. bottom: 0,
  74. left: 0,
  75. right: 0,
  76. height: 24,
  77. display: 'flex',
  78. alignItems: 'center',
  79. justifyContent: 'center',
  80. gap: 60,
  81. zIndex: 10,
  82. },
  83. navButton: {
  84. width: 36,
  85. height: 4,
  86. background: 'rgba(0,0,0,0.3)',
  87. borderRadius: 999,
  88. },
  89. };
  90. function AndroidFrame({
  91. children,
  92. width = 412,
  93. height = 892,
  94. time = '9:41',
  95. battery = 100,
  96. darkMode = false,
  97. navStyle = 'gesture',
  98. }) {
  99. const textColor = darkMode ? '#fff' : '#1a1a1a';
  100. return (
  101. <div style={androidFrameStyles.wrapper}>
  102. <div style={{
  103. ...androidFrameStyles.screen,
  104. width,
  105. height,
  106. background: darkMode ? '#000' : '#fff',
  107. }}>
  108. <div style={{ ...androidFrameStyles.statusBar, color: textColor }}>
  109. <span>{time}</span>
  110. <div style={androidFrameStyles.statusIcons}>
  111. <svg width="14" height="10" viewBox="0 0 14 10" fill="currentColor">
  112. <rect x="0" y="6" width="2" height="4" rx="0.5" />
  113. <rect x="4" y="4" width="2" height="6" rx="0.5" />
  114. <rect x="8" y="2" width="2" height="8" rx="0.5" />
  115. <rect x="12" y="0" width="2" height="10" rx="0.5" />
  116. </svg>
  117. <svg width="14" height="10" viewBox="0 0 14 10" fill="none">
  118. <path d="M7 9a1 1 0 100-2 1 1 0 000 2z" fill="currentColor" />
  119. <path d="M3 6a5 5 0 018 0" stroke="currentColor" strokeWidth="1.2" />
  120. <path d="M0.5 3.5a11 11 0 0113 0" stroke="currentColor" strokeWidth="1.2" opacity="0.6" />
  121. </svg>
  122. <div style={{
  123. width: 22,
  124. height: 10,
  125. border: '1.5px solid currentColor',
  126. borderRadius: 2,
  127. padding: 1,
  128. position: 'relative',
  129. }}>
  130. <div style={{
  131. width: `${battery}%`,
  132. height: '100%',
  133. background: 'currentColor',
  134. borderRadius: 1,
  135. }} />
  136. </div>
  137. <span style={androidFrameStyles.batteryText}>{battery}%</span>
  138. </div>
  139. </div>
  140. <div style={androidFrameStyles.punchHole} />
  141. <div style={androidFrameStyles.content}>
  142. {children}
  143. </div>
  144. {navStyle === 'gesture' && (
  145. <div style={androidFrameStyles.navBar}>
  146. <div style={{
  147. ...androidFrameStyles.navButton,
  148. width: 100,
  149. height: 4,
  150. background: darkMode ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.4)',
  151. }} />
  152. </div>
  153. )}
  154. {navStyle === 'buttons' && (
  155. <div style={androidFrameStyles.navBar}>
  156. <span style={{ color: textColor, fontSize: 20 }}>◁</span>
  157. <span style={{ color: textColor, fontSize: 16 }}>○</span>
  158. <span style={{ color: textColor, fontSize: 16 }}>□</span>
  159. </div>
  160. )}
  161. </div>
  162. </div>
  163. );
  164. }
  165. if (typeof window !== 'undefined') {
  166. window.AndroidFrame = AndroidFrame;
  167. }