ios_frame.jsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /**
  2. * IosFrame — iPhone设备边框
  3. *
  4. * 参考iPhone 15 Pro(393×852 logical pixels)
  5. * 含:灵动岛 + 状态栏(时间/信号/电池)+ Home Indicator + 圆角
  6. *
  7. * 用法:
  8. * <IosFrame time="9:41" battery={85}>
  9. * <YourAppContent />
  10. * </IosFrame>
  11. *
  12. * 自定义:
  13. * <IosFrame width={390} height={844} darkMode showKeyboard>
  14. * ...
  15. * </IosFrame>
  16. */
  17. const iosFrameStyles = {
  18. wrapper: {
  19. display: 'inline-block',
  20. padding: 12,
  21. background: '#000',
  22. borderRadius: 60,
  23. boxShadow: '0 0 0 2px #1f2937, 0 20px 60px rgba(0,0,0,0.3)',
  24. position: 'relative',
  25. },
  26. screen: {
  27. position: 'relative',
  28. borderRadius: 48,
  29. overflow: 'hidden',
  30. background: '#fff',
  31. },
  32. statusBar: {
  33. position: 'absolute',
  34. top: 0,
  35. left: 0,
  36. right: 0,
  37. height: 54,
  38. display: 'flex',
  39. alignItems: 'center',
  40. justifyContent: 'space-between',
  41. padding: '0 32px 0 32px',
  42. fontSize: 16,
  43. fontWeight: 600,
  44. fontFamily: '-apple-system, "SF Pro Text", sans-serif',
  45. zIndex: 20,
  46. pointerEvents: 'none',
  47. },
  48. dynamicIsland: {
  49. position: 'absolute',
  50. top: 12,
  51. left: '50%',
  52. transform: 'translateX(-50%)',
  53. width: 124,
  54. height: 36,
  55. background: '#000',
  56. borderRadius: 999,
  57. zIndex: 30,
  58. },
  59. statusIcons: {
  60. display: 'flex',
  61. alignItems: 'center',
  62. gap: 6,
  63. },
  64. signalIcon: {
  65. display: 'flex',
  66. alignItems: 'flex-end',
  67. gap: 2,
  68. height: 12,
  69. },
  70. signalBar: {
  71. width: 3,
  72. background: 'currentColor',
  73. borderRadius: 1,
  74. },
  75. wifiIcon: {
  76. width: 16,
  77. height: 12,
  78. position: 'relative',
  79. },
  80. batteryIcon: {
  81. width: 26,
  82. height: 12,
  83. border: '1.5px solid currentColor',
  84. borderRadius: 3,
  85. padding: 1,
  86. position: 'relative',
  87. opacity: 0.8,
  88. },
  89. batteryCap: {
  90. position: 'absolute',
  91. top: 3,
  92. right: -3,
  93. width: 2,
  94. height: 6,
  95. background: 'currentColor',
  96. borderRadius: '0 1px 1px 0',
  97. },
  98. content: {
  99. position: 'absolute',
  100. top: 54,
  101. left: 0,
  102. right: 0,
  103. bottom: 34,
  104. overflow: 'auto',
  105. },
  106. homeIndicator: {
  107. position: 'absolute',
  108. bottom: 10,
  109. left: '50%',
  110. transform: 'translateX(-50%)',
  111. width: 140,
  112. height: 5,
  113. background: 'rgba(0,0,0,0.3)',
  114. borderRadius: 999,
  115. zIndex: 10,
  116. },
  117. homeIndicatorDark: {
  118. background: 'rgba(255,255,255,0.5)',
  119. },
  120. };
  121. function IosFrame({
  122. children,
  123. width = 393,
  124. height = 852,
  125. time = '9:41',
  126. battery = 100,
  127. darkMode = false,
  128. showStatusBar = true,
  129. showDynamicIsland = true,
  130. showHomeIndicator = true,
  131. }) {
  132. const textColor = darkMode ? '#fff' : '#000';
  133. return (
  134. <div style={iosFrameStyles.wrapper}>
  135. <div style={{
  136. ...iosFrameStyles.screen,
  137. width,
  138. height,
  139. background: darkMode ? '#000' : '#fff',
  140. }}>
  141. {showStatusBar && (
  142. <div style={{ ...iosFrameStyles.statusBar, color: textColor }}>
  143. <span>{time}</span>
  144. <div style={iosFrameStyles.statusIcons}>
  145. <div style={iosFrameStyles.signalIcon}>
  146. <div style={{ ...iosFrameStyles.signalBar, height: 4 }} />
  147. <div style={{ ...iosFrameStyles.signalBar, height: 6 }} />
  148. <div style={{ ...iosFrameStyles.signalBar, height: 9 }} />
  149. <div style={{ ...iosFrameStyles.signalBar, height: 11 }} />
  150. </div>
  151. <svg width="16" height="12" viewBox="0 0 16 12" fill="none" style={{ color: textColor }}>
  152. <path d="M8 11.5a1 1 0 100-2 1 1 0 000 2z" fill="currentColor" />
  153. <path d="M3 7.5a7 7 0 0110 0" stroke="currentColor" strokeWidth="1.3" fill="none" strokeLinecap="round" />
  154. <path d="M1 4.5a11 11 0 0114 0" stroke="currentColor" strokeWidth="1.3" fill="none" strokeLinecap="round" opacity="0.7" />
  155. </svg>
  156. <div style={iosFrameStyles.batteryIcon}>
  157. <div style={{
  158. width: `${battery}%`,
  159. height: '100%',
  160. background: 'currentColor',
  161. borderRadius: 1,
  162. opacity: 0.9,
  163. }} />
  164. <div style={iosFrameStyles.batteryCap} />
  165. </div>
  166. </div>
  167. </div>
  168. )}
  169. {showDynamicIsland && <div style={iosFrameStyles.dynamicIsland} />}
  170. <div style={iosFrameStyles.content}>
  171. {children}
  172. </div>
  173. {showHomeIndicator && (
  174. <div style={{
  175. ...iosFrameStyles.homeIndicator,
  176. ...(darkMode ? iosFrameStyles.homeIndicatorDark : {}),
  177. }} />
  178. )}
  179. </div>
  180. </div>
  181. );
  182. }
  183. if (typeof window !== 'undefined') {
  184. window.IosFrame = IosFrame;
  185. }