script.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. // FeHelper Website JavaScript
  2. // DOM Ready
  3. document.addEventListener('DOMContentLoaded', function() {
  4. initNavigation();
  5. initToolTabs();
  6. initScrollAnimations();
  7. initParallaxEffects();
  8. // fetchGitHubStats(); // 已移除,无实际用途
  9. initSmoothScrolling();
  10. initPreviewImageModal();
  11. });
  12. // Navigation functionality
  13. function initNavigation() {
  14. const navbar = document.querySelector('.navbar');
  15. const navToggle = document.querySelector('.nav-toggle');
  16. const navMenu = document.querySelector('.nav-menu');
  17. // Navbar scroll effect
  18. window.addEventListener('scroll', () => {
  19. if (window.scrollY > 50) {
  20. navbar.style.background = 'rgba(255, 255, 255, 0.98)';
  21. navbar.style.boxShadow = '0 4px 6px -1px rgb(0 0 0 / 0.1)';
  22. } else {
  23. navbar.style.background = 'rgba(255, 255, 255, 0.95)';
  24. navbar.style.boxShadow = 'none';
  25. }
  26. });
  27. // Mobile menu toggle
  28. if (navToggle && navMenu) {
  29. navToggle.addEventListener('click', () => {
  30. navMenu.classList.toggle('active');
  31. navToggle.classList.toggle('active');
  32. });
  33. // Close menu when clicking on links
  34. navMenu.querySelectorAll('a').forEach(link => {
  35. link.addEventListener('click', () => {
  36. navMenu.classList.remove('active');
  37. navToggle.classList.remove('active');
  38. });
  39. });
  40. }
  41. }
  42. // Tool tabs functionality
  43. function initToolTabs() {
  44. const tabButtons = document.querySelectorAll('.tab-btn');
  45. const toolGrids = document.querySelectorAll('.tools-grid');
  46. tabButtons.forEach(button => {
  47. button.addEventListener('click', () => {
  48. const category = button.dataset.category;
  49. // Remove active class from all buttons and grids
  50. tabButtons.forEach(btn => btn.classList.remove('active'));
  51. toolGrids.forEach(grid => grid.classList.remove('active'));
  52. // Add active class to clicked button and corresponding grid
  53. button.classList.add('active');
  54. const targetGrid = document.querySelector(`[data-category="${category}"].tools-grid`);
  55. if (targetGrid) {
  56. targetGrid.classList.add('active');
  57. }
  58. // Animate the transition
  59. animateGridSwitch(targetGrid);
  60. });
  61. });
  62. }
  63. // Animate grid switch
  64. function animateGridSwitch(grid) {
  65. if (!grid) return;
  66. const cards = grid.querySelectorAll('.tool-card');
  67. cards.forEach((card, index) => {
  68. card.style.opacity = '0';
  69. card.style.transform = 'translateY(20px)';
  70. setTimeout(() => {
  71. card.style.transition = 'all 0.5s ease';
  72. card.style.opacity = '1';
  73. card.style.transform = 'translateY(0)';
  74. }, index * 100);
  75. });
  76. }
  77. // Scroll animations
  78. function initScrollAnimations() {
  79. const observerOptions = {
  80. threshold: 0.1,
  81. rootMargin: '0px 0px -50px 0px'
  82. };
  83. const observer = new IntersectionObserver((entries) => {
  84. entries.forEach(entry => {
  85. if (entry.isIntersecting) {
  86. entry.target.style.opacity = '1';
  87. entry.target.style.transform = 'translateY(0)';
  88. // Special animations for different elements
  89. if (entry.target.classList.contains('feature-card')) {
  90. entry.target.style.animationDelay = '0.2s';
  91. entry.target.classList.add('animate-fade-in-up');
  92. } else if (entry.target.classList.contains('tool-card')) {
  93. entry.target.classList.add('animate-scale-in');
  94. } else if (entry.target.classList.contains('stat-item')) {
  95. animateCounter(entry.target);
  96. }
  97. }
  98. });
  99. }, observerOptions);
  100. // Observe elements for animation
  101. const animatedElements = document.querySelectorAll('.feature-card, .tool-card, .browser-card, .stat-item');
  102. animatedElements.forEach(el => {
  103. el.style.opacity = '0';
  104. el.style.transform = 'translateY(20px)';
  105. el.style.transition = 'all 0.6s ease';
  106. observer.observe(el);
  107. });
  108. }
  109. // Counter animation
  110. function animateCounter(element) {
  111. const numberElement = element.querySelector('.stat-number');
  112. if (!numberElement) return;
  113. const finalNumber = numberElement.textContent;
  114. const numericValue = parseInt(finalNumber.replace(/[^\d]/g, ''));
  115. const suffix = finalNumber.replace(/[\d.]/g, '');
  116. let currentNumber = 0;
  117. const increment = numericValue / 50;
  118. const timer = setInterval(() => {
  119. currentNumber += increment;
  120. if (currentNumber >= numericValue) {
  121. numberElement.textContent = finalNumber;
  122. clearInterval(timer);
  123. } else {
  124. numberElement.textContent = Math.floor(currentNumber) + suffix;
  125. }
  126. }, 50);
  127. }
  128. // Parallax effects
  129. function initParallaxEffects() {
  130. window.addEventListener('scroll', () => {
  131. const scrolled = window.pageYOffset;
  132. const heroPattern = document.querySelector('.hero-pattern');
  133. const browserMockup = document.querySelector('.browser-mockup');
  134. if (heroPattern) {
  135. heroPattern.style.transform = `translateY(${scrolled * 0.5}px)`;
  136. }
  137. if (browserMockup && scrolled < window.innerHeight) {
  138. browserMockup.style.transform = `perspective(1000px) rotateY(-5deg) rotateX(5deg) translateY(${scrolled * 0.1}px)`;
  139. }
  140. });
  141. }
  142. // Smooth scrolling
  143. function initSmoothScrolling() {
  144. document.querySelectorAll('a[href^="#"]').forEach(anchor => {
  145. anchor.addEventListener('click', function (e) {
  146. e.preventDefault();
  147. const target = document.querySelector(this.getAttribute('href'));
  148. if (target) {
  149. const headerOffset = 70;
  150. const elementPosition = target.getBoundingClientRect().top;
  151. const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
  152. window.scrollTo({
  153. top: offsetPosition,
  154. behavior: 'smooth'
  155. });
  156. }
  157. });
  158. });
  159. }
  160. // Add some interactive effects
  161. function addInteractiveEffects() {
  162. // Tool card hover effects
  163. document.querySelectorAll('.tool-card').forEach(card => {
  164. card.addEventListener('mouseenter', function() {
  165. this.style.transform = 'translateY(-8px) scale(1.02)';
  166. });
  167. card.addEventListener('mouseleave', function() {
  168. this.style.transform = 'translateY(0) scale(1)';
  169. });
  170. });
  171. // Browser mockup interaction
  172. const browserMockup = document.querySelector('.browser-mockup');
  173. if (browserMockup) {
  174. let isAnimating = false;
  175. browserMockup.addEventListener('mouseenter', function() {
  176. if (!isAnimating) {
  177. isAnimating = true;
  178. this.style.transform = 'perspective(1000px) rotateY(0deg) rotateX(0deg) scale(1.05)';
  179. setTimeout(() => {
  180. isAnimating = false;
  181. }, 300);
  182. }
  183. });
  184. browserMockup.addEventListener('mouseleave', function() {
  185. this.style.transform = 'perspective(1000px) rotateY(-5deg) rotateX(5deg) scale(1)';
  186. });
  187. }
  188. // Download button effects
  189. document.querySelectorAll('.download-btn').forEach(btn => {
  190. btn.addEventListener('mouseenter', function() {
  191. const arrow = this.querySelector('.btn-arrow');
  192. if (arrow) {
  193. arrow.style.transform = 'translateX(8px)';
  194. }
  195. });
  196. btn.addEventListener('mouseleave', function() {
  197. const arrow = this.querySelector('.btn-arrow');
  198. if (arrow) {
  199. arrow.style.transform = 'translateX(0)';
  200. }
  201. });
  202. });
  203. }
  204. // Initialize interactive effects after DOM is loaded
  205. document.addEventListener('DOMContentLoaded', addInteractiveEffects);
  206. // Performance optimization: Throttle scroll events
  207. function throttle(func, limit) {
  208. let inThrottle;
  209. return function() {
  210. const args = arguments;
  211. const context = this;
  212. if (!inThrottle) {
  213. func.apply(context, args);
  214. inThrottle = true;
  215. setTimeout(() => inThrottle = false, limit);
  216. }
  217. }
  218. }
  219. // Apply throttling to scroll events
  220. window.addEventListener('scroll', throttle(function() {
  221. // Scroll-based animations here
  222. }, 16)); // ~60fps
  223. // Add CSS animations dynamically
  224. const style = document.createElement('style');
  225. style.textContent = `
  226. @keyframes animate-fade-in-up {
  227. from {
  228. opacity: 0;
  229. transform: translateY(30px);
  230. }
  231. to {
  232. opacity: 1;
  233. transform: translateY(0);
  234. }
  235. }
  236. @keyframes animate-scale-in {
  237. from {
  238. opacity: 0;
  239. transform: scale(0.9);
  240. }
  241. to {
  242. opacity: 1;
  243. transform: scale(1);
  244. }
  245. }
  246. .animate-fade-in-up {
  247. animation: animate-fade-in-up 0.6s ease forwards;
  248. }
  249. .animate-scale-in {
  250. animation: animate-scale-in 0.6s ease forwards;
  251. }
  252. /* Mobile menu styles */
  253. @media (max-width: 768px) {
  254. .nav-menu {
  255. position: fixed;
  256. top: 70px;
  257. left: 0;
  258. right: 0;
  259. background: white;
  260. flex-direction: column;
  261. padding: 20px;
  262. box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  263. transform: translateY(-100%);
  264. opacity: 0;
  265. visibility: hidden;
  266. transition: all 0.3s ease;
  267. }
  268. .nav-menu.active {
  269. transform: translateY(0);
  270. opacity: 1;
  271. visibility: visible;
  272. }
  273. .nav-toggle.active span:nth-child(1) {
  274. transform: rotate(45deg) translate(5px, 5px);
  275. }
  276. .nav-toggle.active span:nth-child(2) {
  277. opacity: 0;
  278. }
  279. .nav-toggle.active span:nth-child(3) {
  280. transform: rotate(-45deg) translate(7px, -6px);
  281. }
  282. }
  283. `;
  284. document.head.appendChild(style);
  285. // Add loading states for GitHub data
  286. function showLoadingState() {
  287. const starsElement = document.getElementById('github-stars');
  288. const forksElement = document.getElementById('github-forks');
  289. if (starsElement) {
  290. starsElement.innerHTML = '<span class="loading"></span>';
  291. }
  292. if (forksElement) {
  293. forksElement.innerHTML = '<span class="loading"></span>';
  294. }
  295. }
  296. // Enhanced error handling
  297. window.addEventListener('error', function(e) {
  298. console.log('Error caught:', e.error);
  299. // Graceful degradation - keep functionality working
  300. });
  301. // Service Worker registration for PWA features (optional)
  302. if ('serviceWorker' in navigator) {
  303. window.addEventListener('load', function() {
  304. // Uncomment if you want to add PWA features
  305. // navigator.serviceWorker.register('/sw.js');
  306. });
  307. }
  308. // Export functions for testing (if needed)
  309. if (typeof module !== 'undefined' && module.exports) {
  310. module.exports = {
  311. formatNumber,
  312. throttle
  313. };
  314. }
  315. // Fetch all platform users via shields.io
  316. async function fetchAllPlatformUsers() {
  317. // Shields.io JSON API
  318. const sources = [
  319. {
  320. name: 'Chrome',
  321. url: 'https://img.shields.io/chrome-web-store/users/pkgccpejnmalmdinmhkkfafefagiiiad.json',
  322. },
  323. {
  324. name: 'Edge',
  325. url: 'https://img.shields.io/microsoftedge/addons/users/feolnkbgcbjmamimpfcnklggdcbgakhe.json',
  326. }
  327. ];
  328. let total = 0;
  329. let details = [];
  330. for (const src of sources) {
  331. try {
  332. const res = await fetch(src.url);
  333. const data = await res.json();
  334. // data.value 例:"200k"
  335. let value = data.value.replace(/[^0-9kK+]/g, '');
  336. let num = 0;
  337. if (value.endsWith('k') || value.endsWith('K')) {
  338. num = parseFloat(value) * 1000;
  339. } else {
  340. num = parseInt(value, 10);
  341. }
  342. total += num;
  343. details.push(`${src.name}: ${data.value}`);
  344. } catch (e) {
  345. details.push(`${src.name}: --`);
  346. }
  347. }
  348. // 格式化总数
  349. let totalStr = total >= 1000 ? (total / 1000).toFixed(1) + 'K+' : total + '+';
  350. const numEl = document.getElementById('all-users-number');
  351. if(numEl) numEl.textContent = totalStr;
  352. const statEl = document.getElementById('all-users-stat');
  353. if(statEl) statEl.title = details.join(',');
  354. }
  355. // 工具界面预览区图片放大查看
  356. function initPreviewImageModal() {
  357. const imgs = document.querySelectorAll('.tool-preview .preview-item img');
  358. imgs.forEach(img => {
  359. img.addEventListener('click', function() {
  360. // 创建弹窗元素
  361. const modal = document.createElement('div');
  362. modal.className = 'img-modal';
  363. modal.innerHTML = `
  364. <span class="img-modal-close" title="关闭">&times;</span>
  365. <img src="${img.src}" alt="${img.alt}" />
  366. `;
  367. document.body.appendChild(modal);
  368. // 关闭事件
  369. modal.querySelector('.img-modal-close').onclick = () => document.body.removeChild(modal);
  370. modal.onclick = (e) => {
  371. if (e.target === modal) document.body.removeChild(modal);
  372. };
  373. });
  374. });
  375. }