├── .browserslistrc ├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── DOCUMENT.md ├── README-zh.md ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── assets │ ├── BuyMeCoffeeQRCode.png │ ├── Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg │ ├── Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917.svg │ ├── Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917.svg │ ├── Download_on_the_Mac_App_Store_Badge_US-UK_RGB_wht_092917.svg │ ├── Vimkey-macOS-store-f.png │ ├── Vimkey-macOS-store-grid.png │ ├── Vimkey-macOS-store-search&open.png │ ├── Vimkey-macOS-store-setting.jpg │ ├── background-dark.jpg │ ├── chrome-icon.svg │ ├── chrome-web-store.png │ ├── chrome-webstore.svg │ ├── features-accessibility.png │ ├── features-privacy-policy.png │ ├── features-smoothscroll.png │ ├── guy-working-at-home-2127164-0.svg │ ├── logo-256.png │ ├── logo.png │ ├── toolbar-icon-32.png │ ├── vimkey-f.png │ ├── vimkey-setting.png │ └── website-home.png ├── index.html └── robots.txt ├── src ├── App.vue ├── log.ts ├── main.ts ├── refs.ts ├── registerServiceWorker.ts ├── router.ts ├── shadertoy-transform.ts ├── shims-vue.d.ts └── views │ ├── Home.vue │ ├── Privacy.vue │ ├── Support.vue │ ├── shader.frag │ └── shader.vert ├── tailwind.config.js ├── tsconfig.json └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | globals: {}, 15 | rules: { 16 | 'semi': [ 17 | 'error', 18 | 'never' 19 | ], 20 | 'quotes': [ 21 | 'error', 22 | 'single', 23 | { 24 | 'allowTemplateLiterals': true 25 | } 26 | ], 27 | 'eqeqeq': 'error', 28 | 'no-else-return': 'error', 29 | 'object-curly-spacing': [ 30 | 'error', 31 | 'always' 32 | ], 33 | 'space-infix-ops': 'error', 34 | 'no-duplicate-imports': 'error', 35 | 'no-var': 'error', 36 | 'prefer-const': 'warn', 37 | 'spaced-comment': [ 38 | 'error', 39 | 'always' 40 | ], 41 | 'indent': [ 42 | 'error', 43 | 4, 44 | { 45 | 'VariableDeclarator': 'first', 46 | 'SwitchCase': 1 47 | } 48 | ] 49 | }, 50 | overrides: [ 51 | { 52 | 'files': [ 53 | '**/__tests__/*.{j,t}s?(x)', 54 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 55 | ], 56 | 'env': { 57 | 'jest': true 58 | } 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build Vimkey website 2 | on: 3 | push: 4 | branches: [ docs ] 5 | pull_request: 6 | types: [ closed ] 7 | branches: [ docs ] 8 | 9 | jobs: 10 | build-deploy: 11 | runs-on: macos-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - run: npm ci 15 | - run: npm run build 16 | - name: deploy 17 | uses: JamesIves/github-pages-deploy-action@releases/v3 18 | with: 19 | CLEAN: true 20 | BRANCH: gh-pages 21 | FOLDER: dist/ 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /DOCUMENT.md: -------------------------------------------------------------------------------- 1 | # Vimkey Help 2 | 3 | When you meet some issues, make sure you have updated to latest Vimkey version. 4 | 5 | ## Custom Disable Rule not Work 6 | 7 | - Solution 1: try reset Disable settings or All Vimkey settings 8 | - Solution 2: reinstall Vimkey 9 | 10 | ## Keyboard Bindings 11 | 12 | On any website use [```?```] (shift+/) key to get Vimkey Keybindings help. 13 | 14 | 15 | ## Help [?] key display missing some keys description 16 | 17 | Reset All Vimkey settings 18 | 19 | ## Website can't compatible with Vimkey 20 | 21 | if you in any website get into some trouble you can use the [i] key to temporarily disable Vimkey or go Vimkey disable rule setting permanently disable this website. 22 | 23 | ## If you have other question or advices 24 | 25 | Contact me in the Vimkey Feedback or on GitHub create a new issue let me know. 26 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 | Vimkey 7 |

8 |

9 | 用键盘操控浏览器,导航、滚动、切换标签、搜索等。 10 |

11 | 12 |

13 | English简体中文 14 |

15 |
16 |
17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | ## 功能简介 33 | 34 |
35 | 36 | 37 | 38 | 39 |

功能丰富

40 | 47 |
48 |
49 | 50 |
51 | 52 | 53 | 54 | 55 |

界面美观,使用简单。

56 | 61 |
62 |
63 | 64 |
65 | 66 | 67 | 68 | 69 |

跨平台、支持多个浏览器

70 |

71 | 支持 Safari Chrome Edge 等浏览器,Safari 支持 macOS / iPadOS / iOS 72 |
73 |
74 | iOS & iPadOS 系统版本要求 15.0+ 75 |
76 | macOS 系统版本要求: macOS 11.0+ 77 |

78 |
79 |
80 | 81 | ## 按键功能 82 | 83 | **导航** 84 | 85 | ``` 86 | J 切换到上一个标签页 87 | K 切换到下一个标签页 88 | H 后退 89 | L 前进 90 | ``` 91 | 92 | **滚动控制** 93 | 94 | ``` 95 | k 向上滚动 96 | j 向下滚动 97 | h 向左滚动 98 | l 向右滚动 99 | u 快速向上滚动 100 | d 快速向下滚动 101 | ``` 102 | 103 | **打开 & 搜索** 104 | ```markdown 105 | f 高亮标记当前页可点击的按钮或者链接 106 | o 打开一个链接(按shift从新窗口中打开) 107 | T 搜索当前已打开的标签页 108 | t 打开默认标签页 109 | P 在新标签中打开当前复制的链接 110 | p 在当前标签中打开当前复制的链接 111 | ``` 112 | 113 | **标签内控制** 114 | 115 | ```markdown 116 | i 临时禁用 Vimkey 117 | gf 选择 iframe 118 | yt 创建一个和当前页相同的标签页 119 | r 刷新页面 120 | x 关闭页面 121 | X 恢复刚刚关闭的页面 122 | gi 选中当前页面中第一个输入框 123 | yy 复制当前页面链接 124 | ``` 125 | 126 | **媒体播放** 127 | 128 | ```markdown 129 | - 降低音量 130 | = 调高音量 131 | m 静音/取消 132 | ``` 133 | 134 | ## 其他 135 | 136 | ```markdown 137 | ? 按键帮助 138 | Esc 取消 139 | ``` 140 | 141 | [//]: # (## 支持 & 帮助) 142 | 143 | [//]: # () 144 | [//]: # (请访问官网支持文档:https://haojen.github.io/vimkey/#/support) 145 | 146 | ## 隐私政策 147 | 当启用 Vimkey 时,您会被告知该扩展会访问敏感信息,这个权限是运行所必要的。Vimkey **从不收集、存储或传输任何信息**,它完全在你的设备上本地运行。 148 | 149 | 在 Safari 中,你可以选择只允许一天或总是允许,你也可以选择只允许在特定的网站或每个网站。您可以在任何时候调整或撤销访问权限。 150 | 151 | ## 特别感谢 152 | 153 | Vimium and Vimari 提供了很多灵感和参考。 154 | 155 | ## 关于 156 | 157 | 我是一个独立开发者,这是我唯一的收入来源,正因如此任何形式的支持对我都十分重要(加星、分享、评论、反馈、请我喝杯咖啡等) 158 | 对我是莫大的认可与激励,让我有更多的动力继续打磨、创造出更好的产品。 159 | 160 | ## 请我喝杯饮料 🥤️ 161 | 162 | 163 | Copyright © HAOZHEN MA 2022 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |

7 | Vimkey 8 |

9 |

10 | Use the Keyboard to control the browser, reducing dependence on the Mouse & Trackpad. 11 |

12 | 13 |

14 | English | 简体中文 15 |

16 | 17 |
18 | 19 |

20 | Vimkey - Use the keyboard control browser | Product Hunt 21 |

22 | 23 |

24 | 25 | 26 | 27 | 28 | 29 | 30 |

31 | 32 |

Features

33 | 34 |
35 | 36 | 37 | 38 | 39 |

Powerful Feature

40 | 48 |
49 |
50 | 51 |
52 | 53 | 54 | 55 | 56 |

Beautiful UI, Easy to Use

57 | 63 |
64 |
65 | 66 |
67 | 68 | 69 | 70 | 71 |

Cross-platform, multi-browser support

72 |

73 | Support Safari Chrome Edge. Safari (macOS / iPadOS / iOS) 74 |
75 |
76 | iOS & iPadOS system requirement: 15.0 or above 77 |
78 | macOS system requirement: macOS 11.0 or above 79 |

80 |
81 |
82 | 83 | 84 | ## Keyboard Bindings 85 | 86 | **Navigation** 87 | 88 | ``` 89 | J Switch to pre tab 90 | K Switch to next tab 91 | H Go back in history 92 | L Go forward in history 93 | ``` 94 | 95 | #### Scroll 96 | 97 | ``` 98 | k Scroll up 99 | j Scroll down 100 | u Fast scroll up 101 | d Fast scroll down 102 | h Scroll left 103 | l Scroll right 104 | ``` 105 | 106 | #### Open & Search 107 | ```markdown 108 | f To highlight current page all button or links 109 | o Open A Url From The Tab Or A New Tab (With Shift) 110 | T Search Tabs 111 | t Open A New Tab 112 | P Open The Clipboard's URL In A New Tab 113 | p Open The Clipboard's URL In The Current Tab 114 | ``` 115 | 116 | #### Tab Control 117 | 118 | ```markdown 119 | i TemporarilyDisableModeDescription 120 | gf Select The Next Frame On Page 121 | yt Duplicate Current Tab 122 | r Refresh Page 123 | X Restore Page 124 | x Close Current Page 125 | gi Focus On First Input Field 126 | yy Copy The Current URL 127 | ``` 128 | 129 | #### Media Control 130 | 131 | ```markdown 132 | - Reduce Sound Volume 133 | = Increase Sound Volume 134 | m Mute/Unmute 135 | ``` 136 | 137 | ## Other 138 | 139 | ``` 140 | ? Get Help 141 | Escape Cancel or blur from input 142 | ``` 143 | 144 |

Privacy Policy

145 | 146 | When enabling Vimkey, you will be warned that the extension will have access to sensitive information. This access is required for the extension to be able to interact with the website. Vimkey never collects, stores, or transmits any information. It runs entirely locally on your device 147 | 148 | in Safari, You can choose to allow just for one day or always and you can also choose to allow just on specific websites or on every website. You can revoke access at any time using Manage Extensions 149 | 150 |

Thanks

151 | 152 | Vimium and Vimari provided me with a lot of inspiration and reference 153 | 154 |

About

155 | 156 | As an indie developer, this is my only source of income, which is why your support means so much to me, and it's a great encourage. 157 | 158 | Star, share, comment, feedback, buy me coffee, etc. 159 | 160 | ## Buy me coffee ☕️ 161 | 162 | 163 | Copyright © HAOZHEN MA 2022 164 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vimkey", 3 | "version": "2.0.1", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "register-service-worker": "^1.7.1", 13 | "vue": "^3.0.0", 14 | "vue-router": "^4.0.0-0", 15 | "vue3-markdown-it": "^1.0.9" 16 | }, 17 | "devDependencies": { 18 | "@tailwindcss/postcss7-compat": "^2.0.2", 19 | "@typescript-eslint/eslint-plugin": "^4.18.0", 20 | "@typescript-eslint/parser": "^4.18.0", 21 | "@vue/cli-plugin-babel": "~4.5.0", 22 | "@vue/cli-plugin-eslint": "~4.5.0", 23 | "@vue/cli-plugin-pwa": "~4.5.0", 24 | "@vue/cli-plugin-router": "~4.5.0", 25 | "@vue/cli-plugin-typescript": "~4.5.0", 26 | "@vue/cli-service": "~4.5.0", 27 | "@vue/compiler-sfc": "^3.0.0", 28 | "@vue/eslint-config-standard": "^5.1.2", 29 | "@vue/eslint-config-typescript": "^7.0.0", 30 | "autoprefixer": "^9", 31 | "eslint": "^6.7.2", 32 | "eslint-plugin-import": "^2.20.2", 33 | "eslint-plugin-node": "^11.1.0", 34 | "eslint-plugin-promise": "^4.2.1", 35 | "eslint-plugin-standard": "^4.0.0", 36 | "eslint-plugin-vue": "^7.0.0", 37 | "postcss": "^7", 38 | "raw-loader": "^4.0.2", 39 | "sass": "^1.26.5", 40 | "sass-loader": "^8.0.2", 41 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", 42 | "typescript": "~4.1.5", 43 | "vue-cli-plugin-tailwind": "~2.0.6" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/assets/BuyMeCoffeeQRCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/BuyMeCoffeeQRCode.png -------------------------------------------------------------------------------- /public/assets/Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /public/assets/Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /public/assets/Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /public/assets/Download_on_the_Mac_App_Store_Badge_US-UK_RGB_wht_092917.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_Mac_App_Store_Badge_US-UK_RGB_wht_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /public/assets/Vimkey-macOS-store-f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/Vimkey-macOS-store-f.png -------------------------------------------------------------------------------- /public/assets/Vimkey-macOS-store-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/Vimkey-macOS-store-grid.png -------------------------------------------------------------------------------- /public/assets/Vimkey-macOS-store-search&open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/Vimkey-macOS-store-search&open.png -------------------------------------------------------------------------------- /public/assets/Vimkey-macOS-store-setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/Vimkey-macOS-store-setting.jpg -------------------------------------------------------------------------------- /public/assets/background-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/background-dark.jpg -------------------------------------------------------------------------------- /public/assets/chrome-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/chrome-web-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/chrome-web-store.png -------------------------------------------------------------------------------- /public/assets/chrome-webstore.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 361 | -------------------------------------------------------------------------------- /public/assets/features-accessibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/features-accessibility.png -------------------------------------------------------------------------------- /public/assets/features-privacy-policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/features-privacy-policy.png -------------------------------------------------------------------------------- /public/assets/features-smoothscroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/features-smoothscroll.png -------------------------------------------------------------------------------- /public/assets/guy-working-at-home-2127164-0.svg: -------------------------------------------------------------------------------- 1 | Asset 15 -------------------------------------------------------------------------------- /public/assets/logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/logo-256.png -------------------------------------------------------------------------------- /public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/logo.png -------------------------------------------------------------------------------- /public/assets/toolbar-icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/toolbar-icon-32.png -------------------------------------------------------------------------------- /public/assets/vimkey-f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/vimkey-f.png -------------------------------------------------------------------------------- /public/assets/vimkey-setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/vimkey-setting.png -------------------------------------------------------------------------------- /public/assets/website-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/website-home.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vimkey 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 54 | 69 | -------------------------------------------------------------------------------- /src/log.ts: -------------------------------------------------------------------------------- 1 | export const SRLOG = (text: string) => `shadertoy-react: ${text}` -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import './registerServiceWorker' 4 | import router from './router' 5 | 6 | createApp(App).use(router).mount('#app') 7 | -------------------------------------------------------------------------------- /src/refs.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export const currentPageState = ref<'Home' | 'Privacy' | 'Support'>('Home') -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready () { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered () { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached () { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound () { 20 | console.log('New content is downloading.') 21 | }, 22 | updated () { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline () { 26 | console.log('No internet connection found. App is running in offline mode.') 27 | }, 28 | error (error) { 29 | console.error('Error during service worker registration:', error) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, RouteRecordRaw, createWebHashHistory } from 'vue-router' 2 | import Home from './views/Home.vue' 3 | import { currentPageState } from '@/refs' 4 | 5 | const routes: Array = [ 6 | { 7 | path: '/', 8 | name: 'Home', 9 | component: Home 10 | }, 11 | // { 12 | // path: '/about', 13 | // name: 'About', 14 | // // route level code-splitting 15 | // // this generates a separate chunk (about.[hash].js) for this route 16 | // // which is lazy-loaded when the route is visited. 17 | // component: () => import(/* webpackChunkName: "about" */ './views/About.vue') 18 | // }, 19 | { 20 | path: '/support', 21 | name: 'Support', 22 | component: () => import(/* webpackChunkName: "support" */ './views/Support.vue') 23 | }, 24 | { 25 | path: '/privacy', 26 | name: 'Privacy', 27 | component: () => import(/* webpackChunkName: "support" */ './views/Privacy.vue') 28 | } 29 | ] 30 | 31 | const router = createRouter({ 32 | routes, 33 | history: createWebHashHistory(process.env.BASE_URL) 34 | }) 35 | 36 | router.beforeEach((to) => { 37 | currentPageState.value = to.name as 'Home' | 'Privacy' | 'Support' 38 | }) 39 | 40 | export default router 41 | -------------------------------------------------------------------------------- /src/shadertoy-transform.ts: -------------------------------------------------------------------------------- 1 | import { SRLOG } from './log' 2 | 3 | const PRECISIONS = ['lowp', 'mediump', 'highp'] 4 | 5 | const FS_MAIN_SHADER = `\nvoid main(void){ 6 | vec4 color = vec4(0.0,0.0,0.0,1.0); 7 | mainImage( color, gl_FragCoord.xy ); 8 | gl_FragColor = color; 9 | }` 10 | 11 | const BASIC_FS = 12 | // Basic shadertoy shader 13 | `void mainImage( out vec4 fragColor, in vec2 fragCoord ) { 14 | vec2 uv = fragCoord/iResolution.xy; 15 | vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4)); 16 | fragColor = vec4(col,1.0); 17 | }` 18 | 19 | const BASIC_VS = ` 20 | attribute vec3 aVertexPosition; 21 | void main(void) { 22 | gl_Position = vec4(aVertexPosition, 1.0); 23 | } 24 | ` 25 | 26 | enum Uniforms { 27 | UNIFORM_TIME = 'iTime', 28 | UNIFORM_TIMEDELTA = 'iTimeDelta', 29 | UNIFORM_DATE = 'iDate', 30 | UNIFORM_FRAME = 'iFrame', 31 | UNIFORM_MOUSE = 'iMouse', 32 | UNIFORM_RESOLUTION = 'iResolution', 33 | // UNIFORM_CHANNEL = 'iChannel', 34 | // UNIFORM_CHANNEL_RESOLUTION = 'iChannelResolution', 35 | UNIFORM_DEVICEORIENTATION = 'iDeviceOrientation', // Uniforms not built-int in shadertoy 36 | } 37 | 38 | type Uniform = { 39 | type: string, 40 | value: number | Array, 41 | }; 42 | 43 | type Props = { 44 | fs: string, 45 | vs?: string, 46 | uniforms?: Array, 47 | clearColor?: Array, 48 | precision: string | 'highp' | 'mediump' | 'lowp', 49 | style?: string, 50 | // eslint-disable-next-line no-undef 51 | contextAttributes: WebGLContextAttributes, 52 | lerp?: number, 53 | devicePixelRatio: number, 54 | canvas: HTMLCanvasElement 55 | }; 56 | 57 | 58 | const defaultProps = { 59 | vs: BASIC_VS, 60 | precision: 'highp', 61 | devicePixelRatio: 1, 62 | contextAttributes: {}, 63 | } 64 | 65 | type Shaders = { fs: string, vs: string }; 66 | 67 | function lerpVal(v0: number, v1: number, t: number) { 68 | return v0 * (1 - t) + v1 * t 69 | } 70 | function insertStringAtIndex(currentString: string, string: string, index: number) { 71 | return index > 0 ? currentString.substring(0, index) + string + currentString.substring(index, currentString.length) : string + currentString 72 | } 73 | 74 | export default class ShadertoyTransform { 75 | uniforms:{ 76 | [k in string]: { 77 | type: string 78 | isNeeded: boolean 79 | value: number[] 80 | } 81 | } 82 | gl: WebGLRenderingContext | null = null; 83 | squareVerticesBuffer: WebGLBuffer | null = null; 84 | shaderProgram!: WebGLProgram | null; 85 | vertexPositionAttribute!: number; 86 | animFrameId?: number; 87 | canvas: HTMLCanvasElement; 88 | mousedown = false; 89 | // eslint-disable-next-line no-undef 90 | canvasPosition: ClientRect; 91 | timer = 0; 92 | lastMouseArr: Array = [0, 0]; 93 | lastTime = 0; 94 | 95 | props: Props 96 | 97 | constructor(props: Props) { 98 | 99 | this.props = props 100 | this.canvas = props.canvas 101 | this.canvasPosition = props.canvas.getBoundingClientRect() 102 | 103 | this.uniforms = { 104 | [Uniforms.UNIFORM_TIME]: { 105 | type: 'float', 106 | isNeeded: false, 107 | value: [0], 108 | }, 109 | [Uniforms.UNIFORM_TIMEDELTA]: { 110 | type: 'float', 111 | isNeeded: false, 112 | value: [0], 113 | }, 114 | [Uniforms.UNIFORM_DATE]: { 115 | type: 'vec4', 116 | isNeeded: false, 117 | value: [0, 0, 0, 0], 118 | }, 119 | [Uniforms.UNIFORM_MOUSE]: { 120 | type: 'vec4', 121 | isNeeded: false, 122 | value: [0, 0, 0, 0], 123 | }, 124 | [Uniforms.UNIFORM_RESOLUTION]: { 125 | type: 'vec2', 126 | isNeeded: false, 127 | value: [0, 0], 128 | }, 129 | [Uniforms.UNIFORM_FRAME]: { 130 | type: 'int', 131 | isNeeded: false, 132 | value: [0], 133 | }, 134 | [Uniforms.UNIFORM_DEVICEORIENTATION]: { 135 | type: 'vec4', 136 | isNeeded: false, 137 | value: [0, 0, 0, 0], 138 | } 139 | } 140 | } 141 | 142 | componentDidMount = () => { 143 | this.initWebGL() 144 | 145 | const { fs, vs, clearColor = [0, 0, 0, 1] } = this.props 146 | const { gl } = this 147 | 148 | if (!gl) return 149 | 150 | gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]) 151 | gl.clearDepth(1.0) 152 | gl.enable(gl.DEPTH_TEST) 153 | gl.depthFunc(gl.LEQUAL) 154 | gl.viewport(0, 0, this.canvas.width, this.canvas.height) 155 | 156 | this.canvas.height = this.canvas.clientHeight 157 | this.canvas.width = this.canvas.clientWidth 158 | 159 | this.initShaders(this.preProcessShaders(fs || BASIC_FS, vs || BASIC_VS)) 160 | this.initBuffers() 161 | this.drawScene(0) 162 | // this.addEventListeners() 163 | this.onResize() 164 | } 165 | 166 | componentWillUnmount(): void { 167 | const { gl } = this 168 | 169 | if (gl) { 170 | gl.getExtension('WEBGL_lose_context')!.loseContext() 171 | 172 | gl.useProgram(null) 173 | gl.deleteProgram(this.shaderProgram) 174 | 175 | this.shaderProgram = null 176 | } 177 | 178 | this.removeEventListeners() 179 | cancelAnimationFrame(this.animFrameId!) 180 | } 181 | 182 | initWebGL(): void { 183 | const { contextAttributes } = this.props 184 | this.gl = this.canvas.getContext('webgl', contextAttributes) 185 | this.gl!.getExtension('OES_standard_derivatives') 186 | this.gl!.getExtension('EXT_shader_texture_lod') 187 | } 188 | 189 | initBuffers(): void { 190 | const { gl } = this 191 | 192 | if (gl === null) return 193 | 194 | this.squareVerticesBuffer = gl.createBuffer() 195 | 196 | gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesBuffer) 197 | 198 | const vertices = [ 199 | 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0, 200 | ] 201 | 202 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW) 203 | } 204 | 205 | addEventListeners(): void { 206 | const options = { 207 | passive: true, 208 | } 209 | 210 | if (this.uniforms.iMouse.isNeeded) { 211 | this.canvas.addEventListener('mousemove', this.mouseMove, options) 212 | this.canvas.addEventListener('mouseout', this.mouseUp, options) 213 | this.canvas.addEventListener('mouseup', this.mouseUp, options) 214 | this.canvas.addEventListener('mousedown', this.mouseDown, options) 215 | 216 | // this.canvas.addEventListener('touchmove', this.mouseMove, options) 217 | // this.canvas.addEventListener('touchend', this.mouseUp, options) 218 | // this.canvas.addEventListener('touchstart', this.mouseDown, options) 219 | } 220 | 221 | if (this.uniforms.iDeviceOrientation.isNeeded) { 222 | window.addEventListener( 223 | 'deviceorientation', 224 | this.onDeviceOrientationChange, 225 | options 226 | ) 227 | } 228 | 229 | window.addEventListener('resize', this.onResize, options) 230 | } 231 | 232 | removeEventListeners = () => { 233 | if (this.uniforms.iMouse.isNeeded) { 234 | this.canvas.removeEventListener('mousemove', this.mouseMove) 235 | this.canvas.removeEventListener('mouseout', this.mouseUp) 236 | this.canvas.removeEventListener('mouseup', this.mouseUp) 237 | this.canvas.removeEventListener('mousedown', this.mouseDown) 238 | 239 | // this.canvas.removeEventListener('touchmove', this.mouseMove) 240 | // this.canvas.removeEventListener('touchend', this.mouseUp) 241 | // this.canvas.removeEventListener('touchstart', this.mouseDown) 242 | } 243 | 244 | if (this.uniforms.iDeviceOrientation.isNeeded) { 245 | window.removeEventListener( 246 | 'deviceorientation', 247 | this.onDeviceOrientationChange, 248 | ) 249 | } 250 | 251 | window.removeEventListener('resize', this.onResize) 252 | }; 253 | 254 | onDeviceOrientationChange = (evt: DeviceOrientationEvent) => { 255 | const { alpha, beta, gamma } = evt 256 | this.uniforms.iDeviceOrientation.value = [ 257 | alpha || 0, 258 | beta || 0, 259 | gamma || 0, 260 | window.screen.orientation.angle, 261 | ] 262 | }; 263 | 264 | mouseDown = (e: MouseEvent) => { 265 | const clientX = e.clientX // || e.changedTouches[0].clientX 266 | const clientY = e.clientY // || e.changedTouches[0].clientY 267 | 268 | const mouseX = clientX - this.canvasPosition.left - window.pageXOffset 269 | const mouseY = 270 | this.canvasPosition.height - 271 | clientY - 272 | this.canvasPosition.top - 273 | window.pageYOffset 274 | 275 | this.mousedown = true 276 | this.uniforms.iMouse.value[2] = mouseX 277 | this.uniforms.iMouse.value[3] = mouseY 278 | 279 | this.lastMouseArr[0] = mouseX 280 | this.lastMouseArr[1] = mouseY 281 | }; 282 | 283 | mouseMove = (e: MouseEvent) => { 284 | this.canvasPosition = this.canvas.getBoundingClientRect() 285 | const { lerp = 1 } = this.props 286 | 287 | const clientX = e.clientX // || e.changedTouches[0].clientX 288 | const clientY = e.clientY // || e.changedTouches[0].clientY 289 | 290 | const mouseX = clientX - this.canvasPosition.left 291 | const mouseY = this.canvasPosition.height - clientY - this.canvasPosition.top 292 | 293 | if (lerp !== 1) { 294 | this.lastMouseArr[0] = mouseX 295 | this.lastMouseArr[1] = mouseY 296 | } else { 297 | this.uniforms.iMouse.value[0] = mouseX 298 | this.uniforms.iMouse.value[1] = mouseY 299 | } 300 | }; 301 | 302 | mouseUp = () => { 303 | this.uniforms.iMouse.value[2] = 0 304 | this.uniforms.iMouse.value[3] = 0 305 | }; 306 | 307 | onResize = () => { 308 | const { gl } = this 309 | const { devicePixelRatio = 1 } = this.props 310 | 311 | if (gl === null) return 312 | 313 | this.canvasPosition = this.canvas.getBoundingClientRect() 314 | 315 | const realToCSSPixels = devicePixelRatio // Force pixel ratio to be one to avoid expensive calculus on retina display 316 | 317 | const displayWidth = Math.floor( 318 | this.canvasPosition.width * realToCSSPixels 319 | ) 320 | 321 | const displayHeight = Math.floor( 322 | this.canvasPosition.height * realToCSSPixels 323 | ) 324 | 325 | gl.canvas.width = displayWidth 326 | gl.canvas.height = displayHeight 327 | 328 | if (this.uniforms.iResolution.isNeeded) { 329 | const rUniform = gl.getUniformLocation( 330 | this.shaderProgram!, 331 | Uniforms.UNIFORM_RESOLUTION 332 | ) 333 | // $FlowFixMe 334 | gl.uniform2fv(rUniform, [gl.canvas.width, gl.canvas.height]) 335 | } 336 | }; 337 | 338 | fpsLimit = 50 339 | previousDelta = new Date().getTime() 340 | drawScene = (timestamp: number) => { 341 | this.animFrameId = requestAnimationFrame(this.drawScene) 342 | 343 | const now = new Date().getTime() 344 | const delta = now - this.previousDelta 345 | 346 | if (this.fpsLimit && delta < 1000 / this.fpsLimit) { 347 | return 348 | } 349 | 350 | const gl = this.gl! 351 | const { lerp = 1 } = this.props 352 | 353 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight) 354 | 355 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 356 | 357 | gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesBuffer) 358 | gl.vertexAttribPointer(this.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0) 359 | 360 | this.setUniforms(timestamp) 361 | 362 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4) 363 | 364 | if (this.uniforms.iMouse.isNeeded && lerp !== 1) { 365 | this.uniforms.iMouse.value[0] = lerpVal( 366 | this.uniforms.iMouse.value[0], 367 | this.lastMouseArr[0], 368 | lerp 369 | ) 370 | this.uniforms.iMouse.value[1] = lerpVal( 371 | this.uniforms.iMouse.value[1], 372 | this.lastMouseArr[1], 373 | lerp 374 | ) 375 | } 376 | 377 | this.previousDelta = now 378 | } 379 | 380 | createShader(type: number, shaderCodeAsText: string): WebGLShader { 381 | const gl = this.gl! 382 | 383 | const shader = gl.createShader(type)! 384 | 385 | gl.shaderSource(shader, shaderCodeAsText) 386 | gl.compileShader(shader) 387 | 388 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 389 | console.warn(SRLOG('Error compiling the shader:'), shaderCodeAsText) 390 | const compilationLog = gl.getShaderInfoLog(shader) 391 | gl.deleteShader(shader) 392 | console.error(SRLOG(`Shader compiler log: ${compilationLog}`)) 393 | } 394 | 395 | return shader 396 | } 397 | 398 | initShaders ({ fs, vs }: Shaders): void { 399 | const gl = this.gl! 400 | // console.log(fs, vs); 401 | const fragmentShader = this.createShader(gl.FRAGMENT_SHADER, fs) 402 | const vertexShader = this.createShader(gl.VERTEX_SHADER, vs) 403 | 404 | this.shaderProgram = gl.createProgram() 405 | 406 | if (this.shaderProgram === null) return 407 | 408 | gl.attachShader(this.shaderProgram, vertexShader) 409 | gl.attachShader(this.shaderProgram, fragmentShader) 410 | gl.linkProgram(this.shaderProgram) 411 | 412 | if (!gl.getProgramParameter(this.shaderProgram, gl.LINK_STATUS)) { 413 | // $FlowFixMe 414 | console.error( 415 | SRLOG( 416 | `Unable to initialize the shader program: ${gl.getProgramInfoLog( 417 | this.shaderProgram 418 | )}` 419 | ) 420 | ) 421 | return 422 | } 423 | 424 | gl.useProgram(this.shaderProgram) 425 | 426 | this.vertexPositionAttribute = gl.getAttribLocation( 427 | this.shaderProgram, 428 | 'aVertexPosition' 429 | ) 430 | gl.enableVertexAttribArray(this.vertexPositionAttribute) 431 | } 432 | 433 | preProcessShaders(fs: string, vs: string): { fs: string, vs: string } { 434 | const { precision, devicePixelRatio = 1 } = this.props 435 | 436 | const dprString = `#define DPR ${devicePixelRatio.toFixed(1)} \n` 437 | const isValidPrecision = PRECISIONS.includes(precision) 438 | const precisionString = `precision ${ isValidPrecision ? precision : PRECISIONS[1]} float; \n` 439 | if (!isValidPrecision) 440 | console.warn( 441 | SRLOG(`wrong precision type ${ precision }, please make sure to pass one of a valid precision lowp, mediump, highp, by default you shader precision will be set to highp.`) 442 | ) 443 | 444 | let fsString = precisionString 445 | .concat(dprString) 446 | .concat(fs) 447 | .replace(/texture\(/g, 'texture2D(') 448 | 449 | const indexOfPrecisionString = fsString.lastIndexOf(precisionString) 450 | 451 | Object.keys(this.uniforms).forEach((uniform) => { 452 | if (!fs.includes(uniform)) return 453 | 454 | fsString = insertStringAtIndex( fsString, 455 | `uniform ${this.uniforms[uniform].type} ${uniform} ; \n`, 456 | indexOfPrecisionString + precisionString.length 457 | ) 458 | this.uniforms[uniform].isNeeded = true 459 | }) 460 | 461 | const isShadertoy = /mainImage/.test(fs) 462 | if (isShadertoy) fsString = fsString.concat(FS_MAIN_SHADER) 463 | 464 | return { 465 | fs: fsString, 466 | vs, 467 | } 468 | } 469 | 470 | setUniforms(timestamp: number): void { 471 | const gl = this.gl! 472 | 473 | if (!this.shaderProgram) return 474 | 475 | const delta = this.lastTime ? (timestamp - this.lastTime) / 1000 : 0 476 | this.lastTime = timestamp 477 | 478 | if (this.uniforms.iMouse.isNeeded) { 479 | const mouseUniform = gl.getUniformLocation( 480 | this.shaderProgram, 481 | Uniforms.UNIFORM_MOUSE 482 | ) 483 | gl.uniform4fv(mouseUniform, this.uniforms.iMouse.value) 484 | } 485 | 486 | // if ( 487 | // this.uniforms.iChannelResolution && 488 | // this.uniforms.iChannelResolution.isNeeded 489 | // ) { 490 | // const channelResUniform = gl.getUniformLocation( 491 | // this.shaderProgram, 492 | // Uniforms.UNIFORM_CHANNEL_RESOLUTION 493 | // ) 494 | // gl.uniform3fv(channelResUniform, this.uniforms.iChannelResolution.value) 495 | // } 496 | 497 | if (this.uniforms.iDeviceOrientation.isNeeded) { 498 | const deviceOrientationUniform = gl.getUniformLocation( 499 | this.shaderProgram, 500 | Uniforms.UNIFORM_DEVICEORIENTATION 501 | ) 502 | gl.uniform4fv( 503 | deviceOrientationUniform, 504 | this.uniforms.iDeviceOrientation.value 505 | ) 506 | } 507 | 508 | if (this.uniforms.iTime.isNeeded) { 509 | const timeUniform = gl.getUniformLocation( 510 | this.shaderProgram, 511 | Uniforms.UNIFORM_TIME 512 | ) 513 | 514 | gl.uniform1f(timeUniform, (this.timer += delta)) 515 | } 516 | 517 | if (this.uniforms.iTimeDelta.isNeeded) { 518 | const timeDeltaUniform = gl.getUniformLocation( 519 | this.shaderProgram, 520 | Uniforms.UNIFORM_TIMEDELTA[0] 521 | ) 522 | gl.uniform1f(timeDeltaUniform, delta) 523 | } 524 | 525 | if (this.uniforms.iDate.isNeeded) { 526 | const d = new Date() 527 | const month = d.getMonth() + 1 528 | const day = d.getDate() 529 | const year = d.getFullYear() 530 | const time = 531 | d.getHours() * 60 * 60 + 532 | d.getMinutes() * 60 + 533 | d.getSeconds() + 534 | d.getMilliseconds() * 0.001 535 | 536 | const dateUniform = gl.getUniformLocation( 537 | this.shaderProgram, 538 | Uniforms.UNIFORM_DATE 539 | ) 540 | 541 | gl.uniform4fv(dateUniform, [year, month, day, time]) 542 | } 543 | 544 | if (this.uniforms.iFrame.isNeeded) { 545 | const timeDeltaUniform = gl.getUniformLocation( 546 | this.shaderProgram, 547 | Uniforms.UNIFORM_FRAME[0] 548 | ) 549 | gl.uniform1i(timeDeltaUniform, this.uniforms.iFrame.value[0]++) 550 | } 551 | } 552 | } -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | 8 | interface WebGLTexture { 9 | _webglTexture: WebGLTexture 10 | } 11 | 12 | declare module '*.md' 13 | declare module '*.frag' 14 | declare module '*.vert' 15 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 117 | 118 | 147 | -------------------------------------------------------------------------------- /src/views/Privacy.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/Support.vue: -------------------------------------------------------------------------------- 1 | 9 | 14 | 15 | 36 | -------------------------------------------------------------------------------- /src/views/shader.frag: -------------------------------------------------------------------------------- 1 | #define TMIN 0.1 2 | #define TMAX 200. 3 | #define RAYMARCH_TIME 128 4 | #define PRECISION 0.05 5 | #define AA 3 6 | #define PI 3.14159 7 | 8 | #define S(v,r) smoothstep( r, r+ 3./iResolution.y, v ) 9 | 10 | //========SDFunctions======== 11 | float sdSphere(vec3 p, vec3 o, float r){ 12 | return length(p-o)-r; 13 | } 14 | //===============TRANSFORM================= 15 | mat2 rotate(float a){ 16 | return mat2(cos(a),sin(a),-sin(a),cos(a)); 17 | } 18 | float smUni( float d1, float d2, float k ) { 19 | float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 ); 20 | return mix( d2, d1, h ) - k*h*(1.0-h); 21 | } 22 | 23 | //===============RENDER=================== 24 | //Scene 25 | float f(vec3 p){ 26 | p.zx*=rotate(iTime); 27 | p.yz*=rotate(iTime); 28 | float k = .7; 29 | float d1 = sdSphere(p,vec3(sin(iTime*.5)),1.3);//center sphere 30 | float d2 = sdSphere(p,vec3(0,2.*sin(iTime),0.),.5); 31 | float d = smUni(d1,d2,k); 32 | 33 | float d3 = sdSphere(p,vec3(sin(iTime+5.),0,2.*sin(iTime+5.)),.9); 34 | d= smUni(d,d3,k); 35 | 36 | float d4 = sdSphere(p,vec3(-cos(iTime+.3),cos(iTime+.3),cos(iTime+.3)),.8); 37 | d= smUni(d,d4,k); 38 | 39 | float d5 = sdSphere(p,vec3(3.*cos(iTime+.7),0.,.5),.3); 40 | d= smUni(d,d5,k); 41 | 42 | return d; 43 | } 44 | float rayMarch(in vec3 ro, in vec3 rd) { 45 | float t = TMIN; 46 | for(int i = 0; i < RAYMARCH_TIME ; i++) { 47 | vec3 p = ro + t * rd; 48 | float d = f(p); 49 | t += d; 50 | if(d < PRECISION || t > TMAX) 51 | break; 52 | } 53 | return t; 54 | } 55 | 56 | // https://iquilezles.org/articles/normalsSDF 57 | vec3 calcNormal(in vec3 p) { 58 | const float h = 0.0001; 59 | const vec2 k = vec2(1, -1); 60 | return normalize(k.xyy * f(p + k.xyy * h) + 61 | k.yyx * f(p + k.yyx * h) + 62 | k.yxy * f(p + k.yxy * h) + 63 | k.xxx * f(p + k.xxx * h)); 64 | } 65 | 66 | mat3 setCamera(in vec3 camtar, in vec3 campos, in float camro){ 67 | vec3 z = normalize(camtar-campos); 68 | vec3 cp = vec3(sin(camro),cos(camro),0.); 69 | vec3 x = normalize(cross(cp,z)); 70 | vec3 y = cross(z,x); 71 | return mat3(x,y,z); 72 | } 73 | vec3 render(vec2 uv){ 74 | vec3 lightPos = vec3(-5., 5.,-5);//light 75 | 76 | //SET Camera 77 | vec3 cam_tar = vec3(-1,3,2);//cam target 78 | vec3 cam_pos = cam_tar +vec3(-10,20,15);//cam position 79 | 80 | 81 | vec3 rd = vec3(uv,9.); //decide view width 82 | rd = normalize(setCamera(cam_tar,cam_pos,0.)*rd);//viewing frustum 83 | 84 | float t = rayMarch(cam_pos,rd);//raymarching 85 | 86 | vec3 color = vec3(0.067);//background 87 | if(t > TMAX) return color; 88 | 89 | vec3 p = cam_pos + t*rd; 90 | vec3 n = calcNormal(p); 91 | 92 | color = n*.5+.5; 93 | // fog 94 | color *= exp(-0.4); 95 | 96 | return color; 97 | } 98 | vec2 getuv(vec2 coord){ 99 | return (2.*coord-iResolution.xy)/iResolution.y; 100 | } 101 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 102 | { 103 | vec2 uv = (2.*fragCoord-iResolution.xy)/iResolution.y; 104 | vec3 color = vec3(0.); 105 | #if AA>1 106 | for(int m = 0; m < AA; m++) { 107 | for(int n = 0; n < AA; n++) { 108 | vec2 offset = 2. * (vec2(float(m), float(n)) / float(AA) - .5); 109 | vec2 uv = getuv(fragCoord + offset); 110 | #else 111 | uv = getuv(fragCoord); 112 | #endif 113 | color += render(uv); 114 | #if AA>1 115 | } 116 | } 117 | color /= float(AA*AA); 118 | #endif 119 | 120 | color = mix(color,vec3(1),0.); 121 | fragColor = vec4(color,1.0); 122 | } 123 | -------------------------------------------------------------------------------- /src/views/shader.vert: -------------------------------------------------------------------------------- 1 | attribute vec3 aVertexPosition; 2 | void main(void) { 3 | gl_Position = vec4(aVertexPosition, 1.0); 4 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.ts', 4 | './src/**/*.vue', 5 | './src/**/*.html', 6 | './public/**/*.html' 7 | ], 8 | darkMode: false, // or 'media' or 'class' 9 | theme: { 10 | extend: {}, 11 | }, 12 | variants: { 13 | extend: {}, 14 | }, 15 | plugins: [], 16 | corePlugins: { 17 | preflight: true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: process.env.NODE_ENV === 'production' 3 | ? '/vimkey/' 4 | : '/', 5 | chainWebpack: config => { 6 | config.module.rule('md') 7 | .test(/\.md$/) 8 | .use('raw-loader') 9 | .loader('raw-loader') 10 | .end() 11 | config.module.rule('fragment') 12 | .test(/\.frag$/) 13 | .use('raw-loader') 14 | .loader('raw-loader') 15 | .end() 16 | config.module.rule('vertex') 17 | .test(/\.vert$/) 18 | .use('raw-loader') 19 | .loader('raw-loader') 20 | .end() 21 | } 22 | } 23 | --------------------------------------------------------------------------------