├── src ├── component │ ├── component.ts │ └── box.ts ├── index.ts └── WD40.ts ├── index.html ├── rollup.config.js ├── .github └── workflows │ └── pre-release.yml ├── package.json ├── README.md └── tsconfig.json /src/component/component.ts: -------------------------------------------------------------------------------- 1 | export type WDBody = Matter.Body; 2 | export type WDElem = JQuery; 3 | 4 | export abstract class WDComponent { 5 | abstract body: WDBody 6 | abstract elem: WDElem 7 | 8 | reposition(replaceElem: WDElem, parentElem: WDElem) { 9 | replaceElem.attr( 10 | "WD-40-disable", "true" 11 | ); 12 | this.elem.replaceWith(replaceElem); 13 | parentElem.append(this.elem); 14 | } 15 | 16 | abstract render(): void 17 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
a
11 |
12 | b 13 |
c
14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import ts from '@rollup/plugin-typescript' 2 | import resolve from '@rollup/plugin-node-resolve' 3 | import serve from 'rollup-plugin-serve' 4 | import livereload from 'rollup-plugin-livereload' 5 | 6 | const isDevelopment = () => { 7 | return process.env.NODE_ENV == 'development'; 8 | } 9 | 10 | export default { 11 | input: './src/index.ts', 12 | output: { 13 | dir: 'dist', 14 | format: 'iife', 15 | entryFileNames: '[name].iife.js' 16 | }, 17 | plugins: [ 18 | ts(), 19 | resolve(), 20 | isDevelopment() && livereload('dist'), 21 | isDevelopment() && serve({ 22 | open: true, 23 | port: 8020, 24 | openPage: '/index.html' 25 | }) 26 | ] 27 | } -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "pre-release" 3 | 4 | on: 5 | push: 6 | branches: 7 | - "main" 8 | tags: 9 | - "*" 10 | 11 | jobs: 12 | pre-release: 13 | name: "Pre Release" 14 | runs-on: "ubuntu-latest" 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | cache: npm 24 | 25 | - name: "Install" 26 | run: "npm install" 27 | - name: "Build" 28 | run: "npm run build" 29 | 30 | - uses: ncipollo/release-action@v1 31 | with: 32 | artifacts: "dist/*.js" 33 | tag: "0.3.0-alpha" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wd-40", 3 | "version": "1.0.0", 4 | "description": "浮动点击的元素(致敬清华源23年愚人节彩蛋)>_<", 5 | "main": "index.ts", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development rollup -cw --bundleConfigAsCjs", 8 | "build": "cross-env NODE_ENV=production rollup -c --bundleConfigAsCjs" 9 | }, 10 | "author": "insorker", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@rollup/plugin-node-resolve": "^15.0.1", 14 | "@rollup/plugin-typescript": "^11.0.0", 15 | "@types/jquery": "^3.5.16", 16 | "@types/matter-js": "^0.18.2", 17 | "cross-env": "^7.0.3", 18 | "cz-conventional-changelog": "^3.3.0", 19 | "jquery": "^3.6.4", 20 | "matter-js": "^0.19.0", 21 | "rollup": "^3.20.2", 22 | "rollup-plugin-livereload": "^2.0.5", 23 | "rollup-plugin-serve": "^2.0.2" 24 | }, 25 | "config": { 26 | "commitizen": { 27 | "path": "./node_modules/cz-conventional-changelog" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { WD40 } from './WD40'; 2 | 3 | // module aliases 4 | var Engine = Matter.Engine, 5 | Render = Matter.Render, 6 | Runner = Matter.Runner, 7 | Composite = Matter.Composite; 8 | 9 | // create an engine 10 | var engine = Engine.create(); 11 | engine.gravity.y = -0.04 12 | 13 | // create a renderer 14 | let render = Render.create({ 15 | // element: document.body, 16 | engine: engine, 17 | options: { 18 | width: $(window).width()!, 19 | height: $(window).height()!, 20 | } 21 | }); 22 | 23 | // create WDBox 24 | let wd40 = new WD40({ 25 | engine: engine, 26 | selector: 'div, li, td' 27 | }); 28 | 29 | let ground = Matter.Bodies.rectangle( 30 | $(window).width()! / 2, -50, $(window).width()!, 100, { isStatic: true } 31 | ); 32 | // add all of the bodies to the world 33 | Composite.add(engine.world, [ground]); 34 | 35 | // run the renderer 36 | Render.run(render); 37 | 38 | // create runner 39 | var runner = Runner.create(); 40 | 41 | // run the engine 42 | Runner.run(runner, engine); 43 | 44 | function step() { 45 | wd40.render(); 46 | requestAnimationFrame(step); 47 | } 48 | requestAnimationFrame(step); -------------------------------------------------------------------------------- /src/WD40.ts: -------------------------------------------------------------------------------- 1 | import { WDBox } from "./component/box"; 2 | import { WDComponent } from "./component/component" 3 | 4 | export interface WDConfig { 5 | engine: Matter.Engine 6 | selector: string 7 | } 8 | 9 | export class WD40 implements WDConfig { 10 | engine: Matter.Engine 11 | selector: string 12 | boxes: WDComponent[] 13 | 14 | constructor(config: WDConfig) { 15 | this.engine = config.engine 16 | this.selector = config.selector 17 | this.boxes = []; 18 | 19 | this.setup(); 20 | } 21 | 22 | setup() { 23 | let engine = this.engine; 24 | let boxes = this.boxes; 25 | 26 | $('body').on('click', this.selector, function(event) { 27 | event.stopPropagation(); 28 | 29 | // prevent multiple click on same element 30 | for (let box of boxes) { 31 | if (box.elem[0] == this) { 32 | return; 33 | } 34 | } 35 | if ($(this).attr("WD-40-disable")) { 36 | return; 37 | } 38 | 39 | let box = new WDBox($(this)); 40 | 41 | Matter.Body.applyForce( 42 | box.body, 43 | { x: event.pageX, y: event.pageY }, 44 | { x: Math.random() / 20, y: Math.random() / 20 } 45 | ); 46 | boxes.push(box); 47 | Matter.Composite.add(engine.world, [box.body]); 48 | }); 49 | } 50 | 51 | render() { 52 | for (let box of this.boxes) { 53 | box.render(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/component/box.ts: -------------------------------------------------------------------------------- 1 | import { WDBody, WDElem, WDComponent } from './component' 2 | 3 | export class WDBox extends WDComponent { 4 | body: WDBody 5 | elem: WDElem 6 | 7 | constructor(elem: WDElem) { 8 | super(); 9 | 10 | const offset = elem.offset()!; 11 | const width = elem.width()!; 12 | const height = elem.height()!; 13 | 14 | this.body = Matter.Bodies.rectangle( 15 | offset.left + width / 2, offset?.top + height / 2, width, height 16 | ); 17 | this.elem = elem; 18 | 19 | // restyle element with absolute position 20 | this.elem.css({ 21 | 'position': 'absolute', 22 | 'top': offset.top, 23 | 'left': offset.left, 24 | 'width': width, 25 | 'height': height, 26 | 'background': 'hsl(0, 0%, 96%)', 27 | 'border-radius': '4px', 28 | 'box-shadow': '0px 0px 10px rgba(0, 0, 0, 25%)' 29 | }); 30 | 31 | // replace the original element with empty div 32 | // move the original element to body 33 | let replaceElem = 34 | $('
').css({ 35 | 'width': width, 36 | 'height': height, 37 | }) 38 | let parentElem = $('body'); 39 | this.reposition(replaceElem, parentElem); 40 | } 41 | 42 | render() { 43 | const { x, y } = this.body.position; 44 | const angle = this.body.angle; 45 | 46 | this.elem.css({ 47 | 'top': `${y - this.elem.height()! / 2}px`, 48 | 'left': `${x - this.elem.width()! / 2}px`, 49 | 'transform': `rotate(${angle}rad)` 50 | }) 51 | } 52 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WD-40 2 | 3 | ## 前言 4 | 5 | [书接上文](https://mirrors.tuna.tsinghua.edu.cn/news/#april-fool-wandering-mirrors) 6 | 7 | 很遗憾没能在4月1号当天体验到这个彩蛋,只是偶然 B 站刷视频给我刷到了。本来只是图一乐,看评论区大神讨论感觉好像可行性挺高的(好像要长脑子了>_<),然后就上 google 搜了搜,您猜怎么着: 8 | 9 | 碰巧,看到了这样一个问答 [Using Matter.js to render to the DOM or React](https://stackoverflow.com/questions/63906218/using-matter-js-to-render-to-the-dom-or-react) 。 10 | 11 | 碰巧,这个回答是两年半前 12 | 13 | 碰巧,Matter.js 官网有类似效果的展示 [gyro](https://brm.io/matter-js/demo/#gyro) 14 | 15 | 大概就确定了技术栈:Matter.js + JQuery.js,但是怎么作用在清华源上呢?我又偷不到清华的服务器,也不好意思直接去抢,最主要来回还得坐高铁没人给我报销路费,所以没去。思来想去,只能用油猴脚本的方式来骗、来偷袭了。 16 | 17 | ## 使用 / Usage 18 | 19 | > 目前仅用于油猴脚本 20 | 21 | 安装脚本:https://greasyfork.org/zh-CN/scripts/463273-wd-40 22 | 23 | 测试靶场:https://mirrors.tuna.tsinghua.edu.cn/ 24 | 25 | ## 开发 / Development 26 | 27 | 1. 运行 `git clone` 和 `npm install` 安装依赖 28 | 29 | 2. RTFSC 30 | 31 | 3. 测试 `npm run dev` 32 | 33 | 如需测试油猴脚本,请选择“添加新脚本”,填入以下内容,并修改`YOUR-GIT-REPOSITERY-PATH`: 34 | 35 | ```js 36 | // ==UserScript== 37 | // @name WD-40-Test 38 | // @namespace http://tampermonkey.net/ 39 | // @version 0.1 40 | // @description try to take over the world! 41 | // @author You 42 | // @match http://*/* 43 | // @match https://*/* 44 | // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 45 | // @grant none 46 | // @require https://code.jquery.com/jquery-3.6.4.min.js 47 | // @require https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js 48 | // @require file:// YOUR-GIT-REPOSITERY-PATH \dist\index.iife.js 49 | // ==/UserScript== 50 | 51 | (function() { 52 | 'use strict'; 53 | 54 | // Your code here... 55 | })(); 56 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "esnext", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | --------------------------------------------------------------------------------