├── .changeset ├── README.md └── config.json ├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .pnpm-debug.log ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build ├── babel.config.js ├── gulp.config.js └── ts.config.js ├── components ├── cloudy │ ├── index.tsx │ └── use-cloud.ts ├── context │ ├── context.tsx │ └── index.ts ├── fog │ └── index.tsx ├── haze │ ├── index.tsx │ └── use-haze.ts ├── hooks │ ├── use-theme.ts │ └── use-transition.ts ├── index.tsx ├── interface │ └── index.ts ├── meteors │ ├── index.tsx │ └── use-meteors.ts ├── partly-cloudy │ ├── index.tsx │ └── use-partly-cloud.ts ├── rain-ring │ ├── index.tsx │ └── use-rain-ring.tsx ├── rain │ ├── index.tsx │ └── use-rain.ts ├── snow │ ├── index.tsx │ └── use-snowflake.ts ├── star-rings │ ├── index.tsx │ └── use-starrings.ts ├── sun │ ├── index.tsx │ └── use-sun.ts ├── theme.ts ├── typing.d.ts └── utils │ ├── angle.ts │ ├── constants.ts │ ├── element.ts │ ├── point.ts │ ├── random.ts │ ├── scene.ts │ └── vertority.ts ├── docs ├── README.md ├── cloudy.md ├── fog.md ├── haze.md ├── meteors.md ├── rain-rings.md ├── rain.md ├── snow.md ├── star-rings.md └── sun.md ├── example ├── .babelrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── build │ ├── config.js │ ├── webpack.common.config.js │ ├── webpack.dev.config.js │ └── webpack.prod.config.js ├── mock │ └── fake.js ├── package.json ├── postcss.config.js ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── index.html │ └── site.webmanifest ├── src │ ├── App.tsx │ ├── api │ │ ├── fake.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── components │ │ ├── WeatherSwitcher.tsx │ │ └── WeatherText.tsx │ ├── constants.ts │ ├── index.tsx │ ├── pages │ │ └── prod │ │ │ ├── cloudy.tsx │ │ │ ├── fog.tsx │ │ │ ├── haze.tsx │ │ │ ├── meteors.tsx │ │ │ ├── partly-cloudy.tsx │ │ │ ├── rain.tsx │ │ │ ├── snow.tsx │ │ │ ├── star-ring.tsx │ │ │ └── sun.tsx │ ├── routes │ │ └── index.tsx │ ├── typings │ │ ├── component.d.ts │ │ ├── rematch.d.ts │ │ └── type.d.ts │ └── utils │ │ └── weather.ts ├── static │ └── font.ttf ├── test │ ├── jest.config.js │ └── jest.setup.js └── tsconfig.json ├── global.d.ts ├── gulpfile.js ├── jest.config.js ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── test └── setupTests.ts ├── tsconfig.json └── typings └── style.d.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "linked": [], 6 | "access": "public", 7 | "baseBranch": "master", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | es 2 | lib 3 | gulpfile.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@aiou'], 3 | rules: { 4 | '@typescript-eslint/no-redeclare': 'off', 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: JiangWeixian # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: jiangweixian 5 | open_collective: qidanta 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://afdian.net/@jiangweixian 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What 2 | 3 | ### Warning 4 | 5 | - [ ] - Any warnings? 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # common 2 | node_modules 3 | 4 | # editor 5 | .DS_Store 6 | .vscode 7 | 8 | # test 9 | converage 10 | cache 11 | jest 12 | 13 | # build 14 | dist 15 | lib 16 | .lib 17 | 18 | # log 19 | lerna-debug.log 20 | 21 | # types 22 | **/*.styl.d.ts 23 | 24 | # bin-template 25 | lib 26 | .lib 27 | 28 | # react-components-lib-tempate 29 | es 30 | -------------------------------------------------------------------------------- /.pnpm-debug.log: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.0.0 4 | 5 | ### Major Changes 6 | 7 | - f2d2313: - enable load custom buidlings 8 | - enable fog with phase 9 | - 381a5fa: enable transition config and fix camer switch 10 | - e71ca73: animation 11 | - cae159e: support night and day theme 12 | - ea560bb: - provide much friendly api like `useTheme` 13 | - extends meshline inside 14 | 15 | # v0.11.0 16 | 17 | - ✨ support `haze` weather type 18 | 19 | # v0.10.0 20 | 21 | - ✨ support `fog` weather type 22 | 23 | # ~ v0.9.0 24 | 25 | - ✨ weather type `cloudy`, `meteors`, `partly-cloudy`, `rain`, `rain-ring`, `snow`, `star-rings`, `sun` 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 JW 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

@threejs-weather

5 | 6 | [![npm](https://img.shields.io/npm/v/threejs-weather.svg?style=flat-square)](https://www.npmjs.org/package/threejs-weather) [![npm](https://img.shields.io/npm/dm/threejs-weather.svg?style=flat-square)](https://www.npmjs.org/package/threejs-weather) [![npm](https://img.shields.io/npm/l/threejs-weather.svg?style=flat-square)](https://www.npmjs.org/package/threejs-weather) 7 | 8 | [📝](/docs/README.md) / [✨](https://threejs-weather.now.sh) 9 | 10 |
11 | 12 | [![ctx-threejs-weather](https://user-images.githubusercontent.com/6839576/84586116-11a39400-ae49-11ea-9333-0833ffc9afcf.png)](https://chrome.google.com/webstore/detail/%E5%B0%8F%E5%B7%9D/gckdnedgcldldbdajllnmbmfhacalini) 13 | 14 | ## install 15 | 16 | `npm install threejs-weather three @react-three/fiber` 17 | 18 | ## demos and documents 19 | 20 | docs can be found [here](/docs/README.md) 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | ## todo 32 | 33 | - [ ] 萤火虫 34 | - [ ] 水波纹 35 | - [x] 雾霾 36 | - [x] online-demo 37 | - [x] 光环 38 | - [x] 云 39 | - [x] 流星 40 | 41 | ## develop 42 | 43 | ```console 44 | pnpm i 45 | ``` 46 | 47 | 1. cd `example` 48 | 2. `pnpm link ../` 49 | 3. `pnpm dev` 50 | -------------------------------------------------------------------------------- /build/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (modules) { 2 | const plugins = [ 3 | [ 4 | require.resolve('@babel/plugin-transform-typescript'), 5 | { 6 | isTSX: true, 7 | }, 8 | ], 9 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }, 'ant'], 10 | ] 11 | return { 12 | presets: [ 13 | require.resolve('@babel/preset-react'), 14 | [ 15 | require.resolve('@babel/preset-env'), 16 | { 17 | modules, 18 | targets: { 19 | browsers: [ 20 | 'last 2 versions', 21 | 'Firefox ESR', 22 | '> 1%', 23 | 'ie >= 9', 24 | 'iOS >= 8', 25 | 'Android >= 4', 26 | ], 27 | }, 28 | }, 29 | ], 30 | ], 31 | plugins, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /build/gulp.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const tsConfig = require('./ts.config') 3 | const babelConfig = require('./babel.config') 4 | 5 | const configs = { 6 | dirs: { 7 | components: path.resolve(__dirname, '../components'), 8 | lib: path.resolve(__dirname, '../lib'), 9 | es: path.resolve(__dirname, '../es'), 10 | devLib: path.resolve(__dirname, '../example/src/components/lib'), 11 | devEs: path.resolve(__dirname, '../example/src/components/es'), 12 | }, 13 | tsConfig: tsConfig(), 14 | getBabelConfig: babelConfig, 15 | } 16 | 17 | module.exports = configs 18 | -------------------------------------------------------------------------------- /build/ts.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const lodash = require('lodash') 4 | 5 | const tsConfigPath = path.resolve(__dirname, '../tsconfig.json') 6 | 7 | module.exports = function () { 8 | let my = {} 9 | if (fs.existsSync(tsConfigPath)) { 10 | my = require(tsConfigPath) 11 | } 12 | return lodash.assign( 13 | { 14 | noUnusedParameters: true, 15 | noUnusedLocals: true, 16 | strictNullChecks: true, 17 | target: 'es6', 18 | jsx: 'preserve', 19 | moduleResolution: 'node', 20 | declaration: true, 21 | // isolatedModules: true, 22 | allowSyntheticDefaultImports: true, 23 | }, 24 | my.compilerOptions, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /components/cloudy/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { a } from '@react-spring/three' 3 | import { Mesh } from 'three' 4 | 5 | import { useClouds, UseCloudsProps, Cloud, useCloud } from './use-cloud' 6 | import { Style } from '../interface' 7 | 8 | type CloudyProps = UseCloudsProps & { 9 | style?: Style 10 | } 11 | 12 | type DarkCloudProps = { 13 | value: Cloud 14 | style?: Style 15 | } 16 | 17 | export const DarkCloud = ({ value, style }: DarkCloudProps) => { 18 | const cloud = useRef() 19 | useCloud(cloud) 20 | return ( 21 | x * value.opacity)} 24 | position={value.startpoint} 25 | > 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | const Cloudy = (props: CloudyProps) => { 33 | const { clouds } = useClouds(props) 34 | const pY = props.style?.opacity.to([0, 1], [2, 0]) 35 | return ( 36 | 37 | {clouds.map((cloud, index) => { 38 | return 39 | })} 40 | 41 | ) 42 | } 43 | 44 | export default Cloudy 45 | -------------------------------------------------------------------------------- /components/cloudy/use-cloud.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef } from 'react' 2 | import { Vector3, Mesh } from 'three' 3 | import { useFrame } from '@react-three/fiber' 4 | 5 | import { DIRS } from '../utils/constants' 6 | import random from '../utils/random' 7 | import { getCoord } from '../utils/scene' 8 | import vertority from '../utils/vertority' 9 | 10 | const DARK_CLOUD_COLORS = ['#21373d', '#535657'] 11 | 12 | export const useCloud = (cloud: React.MutableRefObject) => { 13 | const dir = useRef(1) 14 | const speed = useRef(Math.random() * 0.001 * random.inRange(DIRS)) 15 | const distance = useRef(0) 16 | useFrame(() => { 17 | if (!cloud.current) { 18 | return 19 | } 20 | distance.current += 0.001 * dir.current 21 | cloud.current.position.x += speed.current * dir.current 22 | cloud.current.position.y -= speed.current * dir.current 23 | if (distance.current <= 0) { 24 | dir.current = -dir.current 25 | speed.current = Math.random() * 0.001 * random.inRange(DIRS) 26 | } else if (distance.current >= 0.1) { 27 | dir.current = -dir.current 28 | speed.current = Math.random() * 0.001 * random.inRange(DIRS) 29 | } 30 | }) 31 | } 32 | 33 | export type UseCloudsProps = { 34 | count?: number 35 | colors?: string[] 36 | } 37 | 38 | export type Cloud = { 39 | radius: number 40 | startpoint: Vector3 41 | opacity: number 42 | color: string 43 | } 44 | 45 | export const useClouds = ( 46 | { count = 10, colors = DARK_CLOUD_COLORS }: UseCloudsProps = { 47 | count: 10, 48 | colors: DARK_CLOUD_COLORS, 49 | }, 50 | ) => { 51 | const coord = useRef(getCoord()).current 52 | const clouds = useMemo(() => { 53 | return new Array(count).fill(0).map(() => { 54 | return { 55 | radius: (Math.random() * coord[0]) / 2, 56 | startpoint: vertority.fromAxis('fromTop').add(new Vector3(0, coord[1], 0)), 57 | opacity: Math.random(), 58 | color: random.inRange(colors), 59 | } as Cloud 60 | }) 61 | }, [colors, coord, count]) 62 | return { 63 | clouds, 64 | startpoint: new Vector3(0, coord[1], 0), 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /components/context/context.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useCallback, useContext, useEffect } from 'react' 2 | import { Canvas, useFrame, useThree } from '@react-three/fiber' 3 | import { Color } from 'three' 4 | import { useSpring, SpringValue } from '@react-spring/three' 5 | import { PerspectiveCamera } from '@react-three/drei' 6 | 7 | import { useTheme, UseThemeProps } from '../hooks/use-theme' 8 | import { FogCamera } from '../fog' 9 | 10 | type Config = { 11 | type?: UseThemeProps['type'] 12 | mode?: UseThemeProps['mode'] 13 | handleChangeType?: (type: UseThemeProps['type']) => void 14 | handleChangeMode?: (mode?: UseThemeProps['mode']) => void 15 | } 16 | 17 | const WeatherContext = createContext({}) 18 | 19 | type WeatherProps = { 20 | children?: React.ReactNode 21 | defaultType?: UseThemeProps['type'] 22 | defaultMode?: UseThemeProps['mode'] 23 | type?: UseThemeProps['type'] 24 | mode?: UseThemeProps['mode'] 25 | extra?: React.ReactNode 26 | } 27 | 28 | const Container = (props: { 29 | children?: React.ReactNode 30 | style: { 31 | backgroundColor?: SpringValue 32 | opacity?: SpringValue 33 | } 34 | type: UseThemeProps['type'] 35 | }) => { 36 | const { scene, camera } = useThree() 37 | useFrame(() => { 38 | scene.background = new Color(props.style.backgroundColor?.get()) 39 | if (props.type !== 'fog' && props.style.backgroundColor?.idle) { 40 | camera.lookAt(0, 0, 0) 41 | camera.position.z = 5 42 | } 43 | }) 44 | return <>{props.children} 45 | } 46 | 47 | export const WeatherProvider = ({ 48 | defaultMode = 'day', 49 | defaultType = 'sun', 50 | ...props 51 | }: WeatherProps) => { 52 | const [type, setType] = useState(defaultType) 53 | const [mode, setMode] = useState(defaultMode) 54 | const controlType = props.type || type 55 | const controlMode = props.mode || mode 56 | const { bind } = useTheme({ type: controlType, mode: controlMode }) 57 | const { style: _style, ...config } = bind() 58 | const [style, api] = useSpring(() => ({ 59 | backgroundColor: _style.backgroundColor, 60 | })) 61 | useEffect(() => { 62 | api.start({ 63 | backgroundColor: _style.backgroundColor, 64 | }) 65 | }, [_style.backgroundColor, api]) 66 | const handleChangeType = useCallback((type: UseThemeProps['type']) => { 67 | setType(type) 68 | }, []) 69 | const handleChangeMode = useCallback((mode: UseThemeProps['mode']) => { 70 | setMode(mode) 71 | }, []) 72 | return ( 73 | 76 | {props.extra} 77 | 78 | {controlType === 'fog' ? ( 79 | 80 | ) : ( 81 | 82 | )} 83 | 84 | {props.children} 85 | 86 | 87 | 88 | ) 89 | } 90 | 91 | export const useWeather = () => useContext(WeatherContext) 92 | export const WeatherConsumer = WeatherContext.Consumer 93 | -------------------------------------------------------------------------------- /components/context/index.ts: -------------------------------------------------------------------------------- 1 | export { WeatherProvider, useWeather, WeatherConsumer } from './context' 2 | -------------------------------------------------------------------------------- /components/fog/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from 'react' 2 | import { useThree, useFrame } from '@react-three/fiber' 3 | import { PerspectiveCamera } from '@react-three/drei' 4 | import { Object3D, MeshPhysicalMaterial, Vector3, Fog as _Fog, Euler } from 'three' 5 | import { a, TransitionState } from '@react-spring/three' 6 | import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' 7 | 8 | import { Style } from '../interface' 9 | 10 | const url = 'https://raw.githubusercontent.com/iondrimba/images/master/buildings.obj' 11 | const loader = new OBJLoader() 12 | 13 | type FogProps = { 14 | style?: Style 15 | p?: TransitionState 16 | url?: string 17 | } 18 | 19 | const color = '#353c3c' 20 | 21 | const CityFog = (props: FogProps) => { 22 | const [group, setGroup] = useState() 23 | const buildingRef = useRef([]) 24 | const { scene } = useThree() 25 | useEffect(() => { 26 | loader.load(props.url || url, (obj) => { 27 | obj.castShadow = true 28 | obj.receiveShadow = true 29 | const models = [...obj.children].map((model) => { 30 | const scale = 0.01 31 | 32 | model.scale.set(scale, scale, scale) 33 | model.receiveShadow = true 34 | model.castShadow = true 35 | 36 | return model 37 | }) 38 | 39 | const boxSize = 3 40 | const meshParams = { 41 | metalness: 0, 42 | roughness: 0.77, 43 | } 44 | const max = 0.009 45 | const min = 0.001 46 | const material = new MeshPhysicalMaterial({ ...meshParams, transparent: true }) 47 | 48 | const buildings = new Object3D() 49 | const gridSize = 40 50 | 51 | for (let i = 0; i < gridSize; i++) { 52 | for (let j = 0; j < gridSize; j++) { 53 | const model = models[Math.floor(Math.random() * Math.floor(models.length))].clone() 54 | ;(model as any).material = material 55 | model.scale.y = Math.random() * (max - min + 0.01) 56 | model.position.x = i * boxSize 57 | model.position.z = j * boxSize 58 | 59 | buildingRef.current.push(model) 60 | buildings.add(model) 61 | } 62 | } 63 | buildings.castShadow = true 64 | buildings.receiveShadow = true 65 | setGroup(buildings) 66 | }) 67 | }, []) 68 | useFrame(() => { 69 | const near = 1 70 | const far = 208 * (props.style?.opacity.get() ?? 0) 71 | scene.fog = new _Fog(color, near, far) 72 | if (buildingRef.current) { 73 | buildingRef.current.forEach((building) => { 74 | building.material.opacity = props.p?.phase === 'leave' ? 0 : props.style?.opacity.get() ?? 1 75 | }) 76 | } 77 | }) 78 | const scale = props.style?.scale.to([0, 1], [0.8, 1]) 79 | return ( 80 | 81 | 82 | {group ? ( 83 | 84 | ) : null} 85 | 86 | 87 | ) 88 | } 89 | 90 | const rotation = [-0.4239391588266323, 0.7010640463834621, 0.2832774959276831] 91 | const position = [127.45293777867074, 62.11080512264083, 137.6247069251716] 92 | 93 | export const FogCamera = () => { 94 | return ( 95 | 101 | ) 102 | } 103 | 104 | const Fog = (props: FogProps) => { 105 | return ( 106 | 107 | 108 | 109 | 110 | {/* sky */} 111 | 116 | 117 | 118 | 119 | {/* land */} 120 | 127 | 128 | 129 | 130 | 131 | ) 132 | } 133 | 134 | export default Fog 135 | -------------------------------------------------------------------------------- /components/haze/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { extend } from '@react-three/fiber' 3 | import { Mesh } from 'three' 4 | import * as meshline from 'threejs-meshline' 5 | import { a } from '@react-spring/three' 6 | 7 | import { useHazeDrop, UseHazeDropProps, UseHazeProps, useHaze } from './use-haze' 8 | import { Style } from '../interface' 9 | 10 | extend(meshline) 11 | 12 | export const Haze = ( 13 | props: UseHazeDropProps & { 14 | style?: Style 15 | }, 16 | ) => { 17 | const haze = useRef() 18 | const mat = useRef() 19 | useHazeDrop(haze, mat, { value: props.value }) 20 | return ( 21 | x * props.value.opacity)}> 22 | 23 | 34 | 35 | ) 36 | } 37 | 38 | type HazeProps = UseHazeProps & { 39 | style?: Style 40 | } 41 | 42 | const Hazes = (props: HazeProps) => { 43 | const { lines } = useHaze(props) 44 | return ( 45 | 46 | {lines.map((haze, index) => { 47 | return 48 | })} 49 | 50 | ) 51 | } 52 | 53 | export default Hazes 54 | -------------------------------------------------------------------------------- /components/haze/use-haze.ts: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { useFrame } from '@react-three/fiber' 3 | 4 | import vertority from '../utils/vertority' 5 | import { computeBoundingbox } from '../utils/element' 6 | import random from '../utils/random' 7 | import { getCoord } from '../utils/scene' 8 | import { DIRS } from '../utils/constants' 9 | import { useRain } from '../rain/use-rain' 10 | import { angle2dir } from '../utils/angle' 11 | 12 | export type Haze = { 13 | vertices: THREE.Vector3[] // rain-drop geom 14 | orientation: 'fromRight' | 'fromTop' | 'fromLeft' // rain-drop direction 15 | leg: number 16 | angle: number // unit=deg 17 | color: string 18 | dashArray: number 19 | opacity: number 20 | } 21 | 22 | const HAZE_COLORS = ['#fff', '#0F203B'] 23 | 24 | export type UseHazeProps = { 25 | count?: number 26 | angle?: number 27 | } 28 | 29 | export const DEFAULT_RAINPROPS = { 30 | count: 100, 31 | angle: -45, 32 | } 33 | 34 | export const useHaze = ({ angle = -45, count = 100 }: UseHazeProps = DEFAULT_RAINPROPS) => { 35 | const { lines } = useRain({ 36 | angle, 37 | count, 38 | colors: HAZE_COLORS, 39 | }) 40 | return { 41 | lines: lines.map((line) => ({ 42 | ...line, 43 | opacity: Math.random() + 0.5, 44 | dashArray: Math.random() * 0.3, 45 | })), 46 | } 47 | } 48 | 49 | export type UseHazeDropProps = { 50 | value: Haze 51 | } 52 | 53 | export const useHazeDrop = ( 54 | hazedrop: React.MutableRefObject, 55 | mat: React.MutableRefObject, 56 | props?: UseHazeDropProps, 57 | ) => { 58 | const friction = useRef( 59 | random.inRange(DIRS) === 1 ? 0 : 1 * 0.01 * Math.round(Math.random() * 10), 60 | ).current 61 | const coord = useRef(getCoord()).current 62 | const vy0 = useRef(0.001 + friction) 63 | // vy0 / vx0 = tan(angle) 64 | const vx0 = useRef(0.001 + friction) 65 | const a = useRef(0.002) 66 | const { offsetTop } = computeBoundingbox(props?.value.vertices[0]) 67 | useFrame(() => { 68 | if (hazedrop.current?.position.y === undefined || !props?.value || !mat.current) { 69 | return 70 | } 71 | // hazedrop加速下落 72 | hazedrop.current.position.y -= vy0.current 73 | // left hazedrop从左到右移动, right hazedrop从右到左移动 74 | hazedrop.current.position.x -= vx0.current * angle2dir(props.value.angle) 75 | // 从无到有比较真实 76 | vy0.current += a.current 77 | vx0.current += a.current 78 | // 判断hazedrop是否出了边界 79 | if (offsetTop + Math.abs(hazedrop.current.position.y) > coord[1] * 2 + props.value.leg) { 80 | const vertor = vertority.fromPlacement(props.value.orientation) 81 | // 随机hazedrop初始位置, 避免loop重复 82 | hazedrop.current.position.set(vertor.x, vertor.y, vertor.z) 83 | vy0.current = 0.0001 84 | vx0.current = 0.0001 85 | } 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /components/hooks/use-theme.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo } from 'react' 2 | 3 | import { Weather } from '../interface' 4 | import { dayTheme, nightTheme } from '../theme' 5 | 6 | export type UseThemeProps = { 7 | type: Weather 8 | mode: 'day' | 'night' 9 | } 10 | 11 | const theme: Record = { 12 | day: dayTheme, 13 | night: nightTheme, 14 | } 15 | 16 | export const useTheme = ({ mode = 'day', type }: UseThemeProps) => { 17 | const config = useMemo(() => { 18 | return theme[mode][type] 19 | }, [mode, type]) 20 | const bind = useCallback(() => { 21 | return { 22 | ...config, 23 | dpr: window.devicePixelRatio, 24 | } 25 | }, [config]) 26 | return { 27 | bind, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /components/hooks/use-transition.ts: -------------------------------------------------------------------------------- 1 | import { useTransition as useSpringTransition } from '@react-spring/core' 2 | 3 | type TransitionProps = { 4 | location: any 5 | delay?: number 6 | } 7 | 8 | export const useTransition = (props: TransitionProps) => { 9 | const transition = useSpringTransition(props.location, { 10 | from: { opacity: 0, scale: [0, 0, 0] }, 11 | enter: { opacity: 1, scale: [1, 1, 1] }, 12 | leave: { opacity: 0, scale: [0, 0, 0] }, 13 | delay: props.delay, 14 | }) 15 | return { 16 | transition, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /components/index.tsx: -------------------------------------------------------------------------------- 1 | import Rain from './rain' 2 | import Sun from './sun' 3 | import StarRings from './star-rings' 4 | import Snow from './snow' 5 | import Meteors from './meteors' 6 | import Cloudy from './cloudy' 7 | import RainRing from './rain-ring' 8 | import PartlyCloudy from './partly-cloudy' 9 | import Fog from './fog' 10 | import Haze from './haze' 11 | import { useTheme } from './hooks/use-theme' 12 | import { useTransition } from './hooks/use-transition' 13 | import { WeatherProvider, useWeather, WeatherConsumer } from './context' 14 | export * as types from './interface' 15 | 16 | export { 17 | Rain, 18 | Sun, 19 | StarRings, 20 | Snow, 21 | Meteors, 22 | Cloudy, 23 | RainRing, 24 | PartlyCloudy, 25 | Fog, 26 | Haze, 27 | useWeather, 28 | useTheme, 29 | useTransition, 30 | WeatherProvider, 31 | WeatherConsumer, 32 | } 33 | -------------------------------------------------------------------------------- /components/interface/index.ts: -------------------------------------------------------------------------------- 1 | import type { SpringValue } from '@react-spring/core' 2 | 3 | export type Orientation = 'fromTop' | 'fromRight' | 'fromLeft' | 'fromBottom' 4 | export type Weather = 5 | | 'cloudy' 6 | | 'fog' 7 | | 'haze' 8 | | 'meteors' 9 | | 'partly-cloudy' 10 | | 'rain' 11 | | 'snow' 12 | | 'sun' 13 | | 'star-rings' 14 | 15 | export type Style = { 16 | opacity: SpringValue 17 | scale: SpringValue 18 | } 19 | -------------------------------------------------------------------------------- /components/meteors/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { Mesh } from 'three' 3 | import { extend } from '@react-three/fiber' 4 | import { a } from '@react-spring/three' 5 | import * as meshline from 'threejs-meshline' 6 | 7 | import { useMeteors, Meteor, useMeteor, UseMeteorsProps } from './use-meteors' 8 | import { Style } from '../interface' 9 | extend(meshline) 10 | 11 | type MeteorProps = { 12 | value: Meteor 13 | style?: Style 14 | } 15 | 16 | const Meteor = ({ value, style }: MeteorProps) => { 17 | const meteor = useRef() 18 | const mat = useRef() 19 | useMeteor(meteor, mat, { value }) 20 | return ( 21 | x * 0.75)}> 22 | 23 | 34 | 35 | ) 36 | } 37 | 38 | type MeteorsProps = UseMeteorsProps & { 39 | style?: Style 40 | } 41 | 42 | const Meteors = (props: MeteorsProps) => { 43 | const { meteors } = useMeteors(props) 44 | return ( 45 | <> 46 | {meteors.map((meteor, index) => { 47 | return 48 | })} 49 | 50 | ) 51 | } 52 | 53 | export default Meteors 54 | -------------------------------------------------------------------------------- /components/meteors/use-meteors.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef } from 'react' 2 | import { Vector3, Mesh } from 'three' 3 | import { useFrame } from '@react-three/fiber' 4 | 5 | import vertority from '../utils/vertority' 6 | import { computeBoundingbox } from '../utils/element' 7 | import { deg2rad } from '../utils/angle' 8 | import { getCoord } from '../utils/scene' 9 | 10 | export type UseMeteorsProps = { 11 | count?: number 12 | angle?: number 13 | } 14 | 15 | export type Meteor = { 16 | angle: number 17 | leg: number 18 | vertices: Vector3[] 19 | hypotenuse: number 20 | color: string 21 | } 22 | 23 | export const useMeteors = ( 24 | { count = 5, angle = 30 }: UseMeteorsProps = { count: 10, angle: 30 }, 25 | ) => { 26 | const meteors = useMemo(() => { 27 | const _angle = deg2rad(angle) 28 | const coord = getCoord() 29 | return new Array(count).fill(0).map(() => { 30 | const leg = Math.random() * coord[1] 31 | // noise vertor for startpoint 32 | const vertor = vertority.fromPlacement('fromRight') 33 | return { 34 | vertices: [ 35 | // endpoint 36 | new Vector3() 37 | .copy(vertor) 38 | // distance vertor 39 | .add(new Vector3(leg / Math.tan(_angle), leg, 0)), 40 | // startpoint 41 | new Vector3().copy(vertor), 42 | ], 43 | angle: _angle, 44 | leg, 45 | hypotenuse: leg / Math.sin(_angle), 46 | color: 'white', 47 | } as Meteor 48 | }) 49 | }, [count, angle]) 50 | return { 51 | meteors, 52 | } 53 | } 54 | 55 | export type UseMeteorProps = { 56 | value: Meteor 57 | } 58 | 59 | export const useMeteor = ( 60 | meteor: React.MutableRefObject, 61 | mat: React.MutableRefObject, 62 | { value }: UseMeteorProps, 63 | ) => { 64 | const vopacity = useRef(0.01) 65 | const { offsetTop } = computeBoundingbox(value.vertices[0]) 66 | const threshold = Math.random() * 8 67 | useFrame(() => { 68 | if (meteor.current?.position.y === undefined || !mat.current) { 69 | return 70 | } 71 | // 判断meteor是否出了边界 72 | if (offsetTop + Math.abs(meteor.current.position.y) > threshold) { 73 | mat.current.uniforms.dashOffset.value -= Math.abs(vopacity.current) 74 | mat.current.opacity -= vopacity.current 75 | } else { 76 | // meteor加速下落 77 | meteor.current.position.y -= Math.sin(value.angle) * Math.abs(vopacity.current) * 10 78 | meteor.current.position.x -= Math.cos(value.angle) * Math.abs(vopacity.current) * 10 79 | } 80 | if (Math.abs(mat.current.uniforms.dashOffset.value) >= 1.1) { 81 | mat.current.opacity = 1 82 | mat.current.uniforms.dashOffset.value = 0 83 | const vertor = vertority.fromAxis('fromRight') 84 | // 随机meteor初始位置, 避免loop重复 85 | meteor.current.position.set(vertor.x, vertor.y, vertor.z) 86 | } 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /components/partly-cloudy/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { Mesh } from 'three' 3 | import { a } from '@react-spring/three' 4 | 5 | import { usePartlyClouds, UsePartlyCloudProps, Cloud, usePartlyCloud } from './use-partly-cloud' 6 | import { Style } from '../interface' 7 | 8 | type CloudyProps = UsePartlyCloudProps 9 | type WhiteCloudProps = { 10 | value: Cloud 11 | style?: Style 12 | } 13 | 14 | export const WhiteCloud = ({ value, style }: WhiteCloudProps) => { 15 | const cloud = useRef() 16 | usePartlyCloud(cloud) 17 | return ( 18 | x * value.opacity)} 20 | ref={cloud} 21 | position={value.startpoint} 22 | > 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | const PartlyCloudy = (props: CloudyProps & Pick) => { 30 | const { clouds } = usePartlyClouds(props) 31 | const pY = props.style?.opacity.to([0, 1], [2, 0]) 32 | return ( 33 | 34 | {clouds.map((cloud, index) => { 35 | return 36 | })} 37 | 38 | ) 39 | } 40 | 41 | export default PartlyCloudy 42 | -------------------------------------------------------------------------------- /components/partly-cloudy/use-partly-cloud.ts: -------------------------------------------------------------------------------- 1 | import { useClouds, UseCloudsProps, useCloud } from '../cloudy/use-cloud' 2 | export { Cloud } from '../cloudy/use-cloud' 3 | 4 | const WHITE_CLOUD_COLORS = ['#fff'] 5 | 6 | export const usePartlyCloud = useCloud 7 | export type UsePartlyCloudProps = UseCloudsProps 8 | 9 | export const usePartlyClouds = ( 10 | { count = 10, colors = WHITE_CLOUD_COLORS }: UsePartlyCloudProps = { 11 | count: 10, 12 | colors: WHITE_CLOUD_COLORS, 13 | }, 14 | ) => { 15 | return useClouds({ 16 | count, 17 | colors, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /components/rain-ring/index.tsx: -------------------------------------------------------------------------------- 1 | import { Mesh } from 'three' 2 | import React, { useRef } from 'react' 3 | 4 | import { useRainRing, useRainRings, UseRainRingsProps, Ring } from './use-rain-ring' 5 | import { Style } from '../interface' 6 | 7 | type RainRingProps = { 8 | value: Ring 9 | style?: Style 10 | } 11 | 12 | export const RainRing = ({ value, style }: RainRingProps) => { 13 | const mesh = useRef() 14 | useRainRing(mesh, { style }) 15 | return ( 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | type RainRingsProps = UseRainRingsProps & { 24 | style?: Style 25 | } 26 | 27 | const RainRings = (props: RainRingsProps) => { 28 | const { rings } = useRainRings(props) 29 | return ( 30 | <> 31 | {rings.map((ring, index) => { 32 | return 33 | })} 34 | 35 | ) 36 | } 37 | 38 | export default RainRings 39 | -------------------------------------------------------------------------------- /components/rain-ring/use-rain-ring.tsx: -------------------------------------------------------------------------------- 1 | import { Mesh, Material, Vector3 } from 'three' 2 | import { useFrame, useThree } from '@react-three/fiber' 3 | import React, { useMemo } from 'react' 4 | 5 | import vertority from '../utils/vertority' 6 | import { Style } from '../interface' 7 | 8 | export type Ring = { 9 | radius: number 10 | startpoint: Vector3 11 | } 12 | 13 | type UseRainRingProps = { 14 | style?: Style 15 | } 16 | 17 | export const useRainRing = ( 18 | rainring: React.MutableRefObject, 19 | props: UseRainRingProps, 20 | ) => { 21 | const { camera } = useThree() 22 | useFrame(() => { 23 | if (!rainring.current || !rainring.current.material) { 24 | return 25 | } 26 | const mat: Material = rainring.current.material as Material 27 | if (camera.rotation.z >= 0 || camera.rotation.y <= 0) { 28 | mat.opacity = 0 29 | return 30 | } 31 | mat.opacity -= 0.01 32 | mat.opacity *= props.style?.opacity.get() ?? 1 33 | rainring.current.scale.x += 0.1 34 | rainring.current.scale.y += 0.1 35 | if ((rainring.current.material as Material).opacity <= 0) { 36 | const vertor = vertority.random() 37 | rainring.current.position.set(vertor.x, vertor.y, vertor.z) 38 | mat.opacity = 0.2 39 | rainring.current.scale.x = 0 40 | rainring.current.scale.y = 0 41 | } 42 | rainring.current.rotation.x = camera.rotation.x 43 | rainring.current.rotation.y = camera.rotation.y 44 | rainring.current.rotation.z = camera.rotation.z 45 | }) 46 | } 47 | 48 | export type UseRainRingsProps = { 49 | count?: number 50 | } 51 | 52 | export const useRainRings = ({ count = 10 }: UseRainRingsProps = { count: 10 }) => { 53 | const rings = useMemo(() => { 54 | return new Array(count).fill(0).map(() => { 55 | return { 56 | radius: Math.random() * 0.1, 57 | startpoint: vertority.random(), 58 | } as Ring 59 | }) 60 | }, [count]) 61 | return { 62 | rings, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /components/rain/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRain, useRaindrop, UseRaindropProps, UseRainProps } from './use-rain' 2 | import { Style } from '../interface' 3 | 4 | import React, { useRef } from 'react' 5 | import { a } from '@react-spring/three' 6 | import { Mesh } from 'three' 7 | import { extend } from '@react-three/fiber' 8 | import * as meshline from 'threejs-meshline' 9 | extend(meshline) 10 | 11 | export const Raindrop = (props: UseRaindropProps) => { 12 | const raindrop = useRef() 13 | const mat = useRef() 14 | useRaindrop(raindrop, mat, { value: props.value, style: props.style }) 15 | return ( 16 | 17 | 18 | 28 | 29 | ) 30 | } 31 | 32 | type RainProps = UseRainProps & { 33 | style?: Style 34 | } 35 | 36 | const Rain = (props: RainProps) => { 37 | const { lines } = useRain(props) 38 | return ( 39 | 40 | {lines.map((raindrop, index) => { 41 | return 42 | })} 43 | 44 | ) 45 | } 46 | 47 | export default Rain 48 | -------------------------------------------------------------------------------- /components/rain/use-rain.ts: -------------------------------------------------------------------------------- 1 | import { useFrame } from '@react-three/fiber' 2 | import * as THREE from 'three' 3 | import React, { useRef, useMemo } from 'react' 4 | import { Vector3 } from 'three' 5 | 6 | import { computeBoundingbox } from '../utils/element' 7 | import random from '../utils/random' 8 | import vertority from '../utils/vertority' 9 | import point from '../utils/point' 10 | import { deg2rad, angle2dir } from '../utils/angle' 11 | import { DIRS } from '../utils/constants' 12 | import { getCoord } from '../utils/scene' 13 | import { Orientation, Style } from '../interface' 14 | 15 | export type Raindrop = { 16 | vertices: THREE.Vector3[] // rain-drop geom 17 | orientation: 'fromRight' | 'fromTop' | 'fromLeft' // rain-drop direction 18 | leg: number 19 | angle: number // unit=deg 20 | color: string 21 | } 22 | 23 | const RAIN_COLORS = ['#cdd1d3', '#fcd337'] 24 | 25 | const angle2placement = (angle?: number) => { 26 | if (!angle || angle === 0) { 27 | return 'fromTop' 28 | } 29 | return angle < 0 ? 'fromRight' : 'fromLeft' 30 | } 31 | 32 | export type UseRainProps = { 33 | count?: number 34 | angle?: number 35 | colors?: string[] 36 | } 37 | 38 | export const DEFAULT_RAINPROPS = { 39 | count: 100, 40 | angle: -45, 41 | } 42 | 43 | export const useRain = ({ 44 | angle = -45, 45 | count = 100, 46 | colors = RAIN_COLORS, 47 | }: UseRainProps = DEFAULT_RAINPROPS) => { 48 | // raindrop start position data 49 | const _angle = deg2rad(angle) 50 | const startpoints = useRef<{ [key: string]: THREE.Vector3 }>({ 51 | fromTop: new THREE.Vector3().fromArray(point.axisxy.top), 52 | fromRight: new THREE.Vector3().fromArray(point.axisxy.right), 53 | fromLeft: new THREE.Vector3().fromArray(point.axisxy.left), 54 | }).current 55 | // radindrop come from which orientation 56 | const comefrom = useRef<{ [key: string]: Orientation[] }>({ 57 | fromLeft: ['fromTop', 'fromRight'], 58 | fromRight: ['fromTop', 'fromLeft'], 59 | fromTop: ['fromTop'], 60 | }).current 61 | const lines = useMemo(() => { 62 | return Array(count) 63 | .fill(0) 64 | .map(() => { 65 | // h / deltax = tan(ang) 66 | // 直角边 67 | const leg = Math.random() * 2 68 | // FIXME: should modify from angle 69 | const orientation = random.inRange(comefrom[angle2placement(_angle)]) 70 | // noise vertor for startpoint 71 | const vertor = vertority 72 | .fromPlacement(orientation) 73 | .add(new Vector3(0, 0, point.fromAxisZ())) 74 | return { 75 | vertices: [ 76 | // endpoint 77 | new THREE.Vector3() 78 | .copy(startpoints[orientation]) 79 | .add(vertor) 80 | // distance vertor 81 | .add(new THREE.Vector3(leg / Math.tan(_angle), leg, 0)), 82 | // startpoint 83 | new THREE.Vector3().copy(startpoints[orientation]).add(vertor), 84 | ], 85 | angle: _angle, 86 | leg, 87 | orientation, 88 | color: random.inRange(colors), 89 | } as Raindrop 90 | }) 91 | }, [_angle, comefrom, count, startpoints, colors]) 92 | return { 93 | lines, 94 | } 95 | } 96 | 97 | export type UseRaindropProps = { 98 | value: Raindrop 99 | style?: Style 100 | } 101 | 102 | export const useRaindrop = ( 103 | raindrop: React.MutableRefObject, 104 | mat: React.MutableRefObject, 105 | props?: UseRaindropProps, 106 | ) => { 107 | // 摩擦系数, raindrop从负数开始运动, 更加随机的效果 108 | const friction = useRef( 109 | random.inRange(DIRS) === 1 ? 0 : -1 * 0.01 * Math.round(Math.random() * 10), 110 | ).current 111 | const coord = useRef(getCoord()).current 112 | const vy0 = useRef(0.0001 + friction) 113 | // vy0 / vx0 = tan(angle) 114 | const vx0 = useRef(0.0001 + friction) 115 | const a = useRef(0.001) 116 | const { offsetTop } = computeBoundingbox(props?.value.vertices[0]) 117 | useFrame(() => { 118 | if (raindrop.current?.position.y === undefined || !props?.value || !mat.current) { 119 | return 120 | } 121 | // raindrop加速下落 122 | raindrop.current.position.y -= vy0.current 123 | // left raindrop从左到右移动, right raindrop从右到左移动 124 | raindrop.current.position.x -= vx0.current * angle2dir(props.value.angle) 125 | // 从无到有比较真实 126 | mat.current.opacity += 0.01 127 | mat.current.opacity *= props.style?.opacity.get() ?? 1 128 | vy0.current += a.current 129 | vx0.current += a.current 130 | // 判断raindrop是否出了边界 131 | if (offsetTop + Math.abs(raindrop.current.position.y) > coord[1] * 2 + props.value.leg) { 132 | const vertor = vertority.fromPlacement(props.value.orientation) 133 | // 随机raindrop初始位置, 避免loop重复 134 | raindrop.current.position.set(vertor.x, vertor.y, vertor.z) 135 | mat.current.opacity = 0 136 | vy0.current = 0.0001 137 | vx0.current = 0.0001 138 | } 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /components/snow/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { Mesh } from 'three' 3 | import { Style } from '../interface' 4 | import { a } from '@react-spring/three' 5 | 6 | import { useSnowflakes, useSnowflake, Snowflake, UseSnowflakesProps } from './use-snowflake' 7 | 8 | type SnowFlakeProps = { 9 | value: Snowflake 10 | style?: Style 11 | } 12 | 13 | const SnowFlake = ({ value, style }: SnowFlakeProps) => { 14 | const flake = useRef() 15 | useSnowflake(flake, { value }) 16 | return ( 17 | 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | type SnowProps = UseSnowflakesProps & { 25 | style?: Style 26 | } 27 | 28 | const Snow = (props: SnowProps) => { 29 | const { snowflakes } = useSnowflakes(props) 30 | return ( 31 | 32 | {snowflakes.map((snowflake, index) => { 33 | return 34 | })} 35 | 36 | ) 37 | } 38 | 39 | export default Snow 40 | -------------------------------------------------------------------------------- /components/snow/use-snowflake.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef } from 'react' 2 | import { Vector3, Mesh } from 'three' 3 | import { useFrame } from '@react-three/fiber' 4 | 5 | import { DIRS } from '../utils/constants' 6 | import random from '../utils/random' 7 | import { computeBoundingbox } from '../utils/element' 8 | import { getCoord } from '../utils/scene' 9 | import vertority from '../utils/vertority' 10 | 11 | export type Snowflake = { 12 | startpoint: Vector3 13 | radius: number 14 | } 15 | 16 | export type UseSnowflakesProps = { 17 | count?: number 18 | } 19 | 20 | export const useSnowflakes = ({ count = 100 }: UseSnowflakesProps = { count: 100 }) => { 21 | const snowflakes = useMemo(() => { 22 | const coord = getCoord() 23 | return new Array(4).fill(0).reduce((prev, _cur, i) => { 24 | return prev.concat( 25 | new Array(Math.round(count / 4)).fill(0).map(() => { 26 | const vertor = vertority.fromPlacement('fromTop') 27 | return { 28 | startpoint: new Vector3().copy(new Vector3(0, coord[1] + i * 2, 0)).add(vertor), 29 | radius: Math.random() * 0.1, 30 | } 31 | }), 32 | ) 33 | }, []) 34 | }, [count]) 35 | return { 36 | snowflakes: snowflakes as Snowflake[], 37 | } 38 | } 39 | 40 | type UseSnowflakeProps = { 41 | value: Snowflake 42 | } 43 | 44 | export const useSnowflake = ( 45 | flake: React.MutableRefObject, 46 | { value }: UseSnowflakeProps, 47 | ) => { 48 | const vy0 = useRef(0.01) 49 | const coord = useRef(getCoord()).current 50 | // vy0 / vx0 = tan(angle) 51 | const vx0 = useRef(0.001 * Math.random() * random.inRange(DIRS)) 52 | // const a = useRef(0.00001) 53 | const { offsetTop } = computeBoundingbox(value.startpoint) 54 | useFrame(() => { 55 | if (!flake.current) { 56 | return 57 | } 58 | // 雪花加速下落 59 | flake.current.position.y -= vy0.current 60 | flake.current.position.x -= vx0.current 61 | // 判断是否出了边界 62 | if (offsetTop + Math.abs(value.startpoint.y - flake.current.position.y) > coord[1] * 2) { 63 | const vertor = vertority.fromAxis('fromTop') 64 | // 随机raindrop初始位置, 避免loop重复 65 | flake.current.position.set(vertor.x, vertor.y + coord[1], vertor.z) 66 | vy0.current = 0.01 67 | vx0.current = 0.001 * Math.random() * random.inRange(DIRS) 68 | } 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /components/star-rings/index.tsx: -------------------------------------------------------------------------------- 1 | import { useStarRings, Ring, useRing, UseStarRingsProps } from './use-starrings' 2 | import React, { useRef } from 'react' 3 | import { extend } from '@react-three/fiber' 4 | import { a } from '@react-spring/three' 5 | 6 | import * as meshline from 'threejs-meshline' 7 | import { Style } from '../interface' 8 | 9 | extend(meshline) 10 | 11 | type RingProps = { 12 | value: Ring 13 | style?: Style 14 | } 15 | 16 | const Ring = ({ value, style }: RingProps) => { 17 | const ring = useRef() 18 | useRing(ring) 19 | return ( 20 | x * value.opacity)}> 21 | 22 | 33 | 34 | ) 35 | } 36 | 37 | type StarRingsProps = UseStarRingsProps & { 38 | style?: Style 39 | } 40 | 41 | const StarRings = (props: StarRingsProps) => { 42 | const { rings, startpoint } = useStarRings(props) 43 | return ( 44 | 45 | {rings.map((ring, index) => { 46 | return 47 | })} 48 | 49 | ) 50 | } 51 | 52 | export default StarRings 53 | -------------------------------------------------------------------------------- /components/star-rings/use-starrings.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef } from 'react' 2 | import * as THREE from 'three' 3 | 4 | import random from '../utils/random' 5 | import { DIRS } from '../utils/constants' 6 | import { useFrame } from '@react-three/fiber' 7 | import { getCoord } from '../utils/scene' 8 | 9 | export type UseStarRingsProps = { 10 | count?: number 11 | } 12 | 13 | export type Ring = { 14 | radius: number 15 | vertices: THREE.Vector3[] 16 | dashArray: number 17 | opacity: number 18 | color: string 19 | lineWidth: number 20 | } 21 | 22 | const RING_COLORS = ['#cdd1d3', '#fcd337', '#1677b3'] 23 | 24 | export const useStarRings = ({ count = 50 }: UseStarRingsProps = { count: 50 }) => { 25 | const coord = useRef(getCoord()).current 26 | const startpoint = useMemo(() => { 27 | return new THREE.Vector3(-coord[0], coord[1], 0) 28 | }, [coord]) 29 | const rings = useMemo(() => { 30 | return new Array(count).fill(0).map(() => { 31 | const radius = Math.random() * 10 32 | const vertices = new Array(180).fill(0).map((_v, i) => { 33 | return new THREE.Vector3( 34 | Math.cos((i * 2 * Math.PI) / 180) * radius, 35 | Math.sin((i * 2 * Math.PI) / 180) * radius, 36 | 0, 37 | ) 38 | }) 39 | return { 40 | radius, 41 | vertices, 42 | dashArray: Math.random() + 0.1, 43 | opacity: Math.random() * 0.8, 44 | color: random.inRange(RING_COLORS), 45 | lineWidth: Math.random() * 0.05, 46 | } as Ring 47 | }) 48 | }, [count]) 49 | return { 50 | rings, 51 | startpoint, 52 | } 53 | } 54 | 55 | export const useRing = (ring: React.MutableRefObject) => { 56 | const dir = random.inRange(DIRS) 57 | const speed = useRef(Math.random() * 2 * 0.0001) 58 | useFrame(() => { 59 | if (!ring.current) { 60 | return 61 | } 62 | ring.current.uniforms.dashOffset.value -= speed.current * dir 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /components/sun/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { Group, Mesh } from 'three' 3 | import { useFrame } from '@react-three/fiber' 4 | import { a } from '@react-spring/three' 5 | 6 | import { useSun, UseSunProps, Halo } from './use-sun' 7 | import { inRange } from '../utils/random' 8 | import { DIRS } from '../utils/constants' 9 | import { Style } from '../interface' 10 | 11 | type SunProps = UseSunProps & { 12 | style?: Style 13 | } 14 | 15 | type HaloProps = { 16 | value: Halo 17 | style?: Style 18 | } 19 | 20 | const SunHalo = ({ value, style }: HaloProps) => { 21 | const dir = useRef(1) 22 | const speed = useRef(Math.random() * 0.0001 * inRange(DIRS)) 23 | const halo = useRef() 24 | useFrame(() => { 25 | if (!halo.current) { 26 | return 27 | } 28 | if (halo.current.scale.x <= 0.9) { 29 | dir.current = -dir.current 30 | } else if (halo.current.scale.x >= 1.1) { 31 | dir.current = -dir.current 32 | } 33 | halo.current.scale.x += speed.current * dir.current 34 | halo.current.scale.y += speed.current * dir.current 35 | }) 36 | return ( 37 | 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | const Sun = (props: SunProps) => { 45 | const sun = useRef() 46 | const { halos, startpoint } = useSun(props) 47 | return ( 48 | 49 | {halos.map((halo, index) => { 50 | return 51 | })} 52 | 53 | ) 54 | } 55 | 56 | export default Sun 57 | -------------------------------------------------------------------------------- /components/sun/use-sun.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useMemo } from 'react' 2 | import { useFrame } from '@react-three/fiber' 3 | import { Group, Vector3 } from 'three' 4 | 5 | import { getCoord } from '../utils/scene' 6 | 7 | const R = 2 8 | const HALO_COLORS = ['#faf3d2', '#fbebb3', '#fae09c', '#f9d67c', '#f6c451', '#c34e35'] 9 | 10 | type Sunshine = { 11 | vertices: Vector3[] 12 | angle: number 13 | } 14 | 15 | export type Halo = { 16 | radius: number 17 | color: string 18 | startpoint: Vector3 19 | } 20 | 21 | export type UseSunProps = { 22 | percentX?: number 23 | count?: number 24 | } 25 | 26 | export const useSun = ( 27 | { percentX = 1, count = HALO_COLORS.length }: UseSunProps = { 28 | percentX: 1, 29 | count: HALO_COLORS.length, 30 | }, 31 | ) => { 32 | const angle = useRef(-(Math.random() * 90 + 90)) 33 | const coord = useRef(getCoord()).current 34 | const startpoint = useMemo(() => { 35 | return new Vector3(coord[0] * percentX, coord[1], 0) 36 | }, [coord, percentX]) 37 | 38 | const sunshines = useMemo(() => { 39 | return Array(3) 40 | .fill(0) 41 | .map((_v, i) => { 42 | angle.current += i * 20 43 | const startpoint = Math.random() * coord[0] + coord[0] * percentX 44 | const length = Math.random() * 2 45 | return { 46 | vertices: [ 47 | new Vector3( 48 | Math.cos((angle.current * Math.PI) / 180) * startpoint, 49 | Math.sin((angle.current * Math.PI) / 180) * startpoint, 50 | 0, 51 | ), 52 | new Vector3( 53 | Math.cos((angle.current * Math.PI) / 180) * (startpoint + length), 54 | Math.sin((angle.current * Math.PI) / 180) * (startpoint + length), 55 | 0, 56 | ), 57 | ], 58 | angle: angle.current, 59 | } as Sunshine 60 | }) 61 | }, [coord, percentX]) 62 | const halos = useMemo(() => { 63 | return Array(count) 64 | .fill(0) 65 | .map((_v, i) => { 66 | const startpoint = new Vector3(0.5 - Math.random() * 1, 0.5 - Math.random() * 1, 0) 67 | return { 68 | radius: R + 5 - i, 69 | color: HALO_COLORS[i], 70 | startpoint, 71 | } as Halo 72 | }) 73 | }, [count]) 74 | return { 75 | sunshines, 76 | halos, 77 | startpoint, 78 | } 79 | } 80 | 81 | export const useSunshine = (sunshine: React.MutableRefObject) => { 82 | useFrame(() => { 83 | if (!sunshine.current) { 84 | return 85 | } 86 | sunshine.current.rotation.z -= 0.001 87 | if (Math.abs(sunshine.current.rotation.z) > 2) { 88 | sunshine.current.rotation.z = 0 89 | } 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /components/theme.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'react' 2 | import { PCFSoftShadowMap } from 'three' 3 | 4 | import { Weather } from './interface' 5 | 6 | export const dayTheme: Record< 7 | Weather, 8 | { 9 | style: CSSProperties 10 | shadows?: any 11 | } 12 | > = { 13 | cloudy: { 14 | style: { 15 | backgroundColor: '#3C4245', 16 | }, 17 | }, 18 | fog: { 19 | style: { 20 | backgroundColor: '#0F203B', 21 | }, 22 | shadows: { enabled: true, type: PCFSoftShadowMap }, 23 | }, 24 | haze: { 25 | style: { 26 | backgroundColor: '#A2915E', 27 | }, 28 | shadows: true, 29 | }, 30 | meteors: { 31 | style: { 32 | backgroundColor: '#0F203B', 33 | }, 34 | }, 35 | 'partly-cloudy': { 36 | style: { 37 | backgroundColor: '#1677b3', 38 | }, 39 | }, 40 | rain: { 41 | style: { 42 | backgroundColor: '#1677b3', 43 | }, 44 | }, 45 | snow: { 46 | style: { 47 | backgroundColor: '#1677b3', 48 | }, 49 | }, 50 | 'star-rings': { 51 | style: { 52 | backgroundColor: '#0F203B', 53 | }, 54 | }, 55 | sun: { 56 | style: { 57 | backgroundColor: '#faf4e8', 58 | }, 59 | }, 60 | } 61 | 62 | export const nightTheme = { 63 | ...dayTheme, 64 | rain: { 65 | ...dayTheme.rain, 66 | style: { 67 | backgroundColor: '#0F203B', 68 | }, 69 | }, 70 | cloudy: { 71 | ...dayTheme.cloudy, 72 | style: { 73 | backgroundColor: '#0F203B', 74 | }, 75 | }, 76 | 'partly-cloudy': { 77 | ...dayTheme['partly-cloudy'], 78 | style: { 79 | backgroundColor: '#0F203B', 80 | }, 81 | }, 82 | snow: { 83 | ...dayTheme.snow, 84 | style: { 85 | backgroundColor: '#0F203B', 86 | }, 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /components/typing.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'three.meshline' 2 | declare module 'threejs-meshline' 3 | -------------------------------------------------------------------------------- /components/utils/angle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * convert deg to rad 3 | * @param {number} deg 4 | */ 5 | export const deg2rad = (deg = 0) => { 6 | return (deg * Math.PI) / 180 7 | } 8 | 9 | /** 10 | * 角度正负数值 11 | * @param {number | undefined} angle 12 | */ 13 | export const angle2dir = (angle?: number) => { 14 | if (!angle || angle === 0) { 15 | return 0 16 | } 17 | return angle / Math.abs(angle) 18 | } 19 | -------------------------------------------------------------------------------- /components/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const DIRS = [1, -1] 2 | -------------------------------------------------------------------------------- /components/utils/element.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three' 2 | import { getCoord } from './scene' 3 | 4 | /** 5 | * box offset to four edge 6 | * @param pos 7 | */ 8 | export const computeBoundingbox = (pos?: Vector3) => { 9 | if (!pos) { 10 | return { 11 | offsetLeft: 0, 12 | offsetRight: 0, 13 | offsetTop: 0, 14 | offsetBottom: 0, 15 | } 16 | } 17 | const [coordx, coordy] = getCoord() 18 | const { x, y } = pos 19 | return { 20 | offsetLeft: x > 0 ? coordx + x : coordx - Math.abs(x), 21 | offsetRight: x > 0 ? coordx - x : coordx + Math.abs(x), 22 | offsetTop: y > 0 ? coordy - y : coordy + Math.abs(y), 23 | offsetBottom: y > 0 ? coordy + y : coordy - Math.abs(y), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /components/utils/point.ts: -------------------------------------------------------------------------------- 1 | import { getCoord } from './scene' 2 | import { atPoint } from './random' 3 | 4 | const [axisx, axisy, axisz] = getCoord() 5 | export const axisxy = { 6 | top: [0, axisy, 0], 7 | bottom: [0, -axisy, 0], 8 | left: [-axisx, 0, 0], 9 | right: [axisx, 0, 0], 10 | } 11 | 12 | export const fromAxisX = () => { 13 | return atPoint() * axisx 14 | } 15 | export const fromAxisY = () => { 16 | return atPoint() * axisy 17 | } 18 | export const fromAxisZ = () => { 19 | return atPoint() * axisz 20 | } 21 | export const fromAxis = (axis: 'x' | 'y' | 'z') => { 22 | switch (axis) { 23 | case 'x': 24 | return fromAxisX() 25 | case 'y': 26 | return fromAxisY() 27 | case 'z': 28 | return fromAxisZ() 29 | default: 30 | return fromAxisX() 31 | } 32 | } 33 | 34 | export default { 35 | fromAxisX, 36 | fromAxisY, 37 | fromAxisZ, 38 | fromAxis, 39 | axisxy, 40 | } 41 | -------------------------------------------------------------------------------- /components/utils/random.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 从range数组中随机一个元素 3 | * @param range any[] 4 | */ 5 | export const inRange = (range: T[]) => { 6 | return range[Math.round(Math.random() * (range.length - 1))] 7 | } 8 | 9 | /** 10 | * scene内部随机点 11 | */ 12 | export const atPoint = () => { 13 | return 1 - Math.random() * 2 14 | } 15 | 16 | export default { 17 | atPoint, 18 | inRange, 19 | } 20 | -------------------------------------------------------------------------------- /components/utils/scene.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * get Coordinate System 3 | */ 4 | export const getCoord = () => { 5 | const w = window.innerWidth 6 | const h = window.innerHeight 7 | let unit = 1 8 | if (w < h) { 9 | unit = w / 4 10 | return [4, h / unit, 4] 11 | } 12 | unit = h / 4 13 | return [w / unit, 4, 4] 14 | } 15 | -------------------------------------------------------------------------------- /components/utils/vertority.ts: -------------------------------------------------------------------------------- 1 | import point from './point' 2 | import { Orientation } from '../interface' 3 | 4 | import { Vector3 } from 'three' 5 | 6 | const PLACEMENT_TO_AXIS: { [key: string]: 'x' | 'y' | 'z' } = { 7 | fromTop: 'x', 8 | fromRight: 'y', 9 | fromLeft: 'y', 10 | fromBottom: 'x', 11 | } 12 | export const PLACEMENT_TO_DIR: { [key in Orientation]: 1 | -1 } = { 13 | fromTop: 1, 14 | fromRight: 1, 15 | fromLeft: -1, 16 | fromBottom: -1, 17 | } 18 | 19 | export const PLACEMENTS = Object.keys(PLACEMENT_TO_DIR) as Orientation[] 20 | /** 21 | * 得到一个随机的噪声向量, 和orientation有关。 22 | * - placement = top -> [a, b, 0](fromPlacement) or [0, b, 0](fromAxis) 23 | * - placement = left -> [a, b, 0] or [a, 0, 0] 24 | * - placement = right -> [a, b, 0] or [a, 0, 0] 25 | * @param {Orientation} placement 26 | * @param props 27 | */ 28 | const fromAxis = (placement: Orientation) => { 29 | const endpoint = { x: 0, y: 0, z: 0 } 30 | endpoint[PLACEMENT_TO_AXIS[placement]] = point.fromAxis(PLACEMENT_TO_AXIS[placement]) 31 | return new Vector3(endpoint.x, endpoint.z, endpoint.z) 32 | } 33 | 34 | const fromPlacement = (placement: Orientation) => { 35 | const endpoint = { x: 0, y: 0, z: 0 } 36 | endpoint[PLACEMENT_TO_AXIS[placement]] = point.fromAxis(PLACEMENT_TO_AXIS[placement]) 37 | if (PLACEMENT_TO_AXIS[placement] === 'x') { 38 | endpoint.y = Math.abs(point.fromAxisY() * 0.5) * PLACEMENT_TO_DIR[placement] 39 | } 40 | if (PLACEMENT_TO_AXIS[placement] === 'y') { 41 | endpoint.x = Math.abs(point.fromAxisY() * 0.5) * PLACEMENT_TO_DIR[placement] 42 | } 43 | return new Vector3(endpoint.x, endpoint.y, endpoint.z) 44 | } 45 | 46 | const random = () => { 47 | return new Vector3(point.fromAxisX(), point.fromAxisY(), point.fromAxisZ()) 48 | } 49 | 50 | export default { 51 | fromAxis, 52 | fromPlacement, 53 | random, 54 | } 55 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # docs 2 | 3 | - [rain](/docs/rain.md) 4 | - [meteors](/docs/meteors.md) 5 | - [sun](/docs/sun.md) 6 | - [snow](/docs/snow.md) 7 | - [star-rings](/docs/star-rings.md) 8 | - [cloudy](/docs/cloudy.md) 9 | - [rain-ring](/docs/rain-rings.md) 10 | -------------------------------------------------------------------------------- /docs/cloudy.md: -------------------------------------------------------------------------------- 1 | ![cloudy](https://user-images.githubusercontent.com/6839576/83318092-191d4780-a264-11ea-9095-29d5ff180247.gif) 2 | 3 | ## usage 4 | 5 | ```tsx 6 | import { Cloudy, useTheme } from 'threejs-weather' 7 | import { Canvas } from '@react-three/fiber' 8 | 9 | const CloudyPage = () => { 10 | const { bind } = useTheme({ type: 'cloudy', mode: 'day' }) 11 | return ( 12 | 13 | 14 | 15 | ) 16 | } 17 | ``` 18 | 19 | ## props 20 | 21 | | name | description | type | default | 22 | | :---: | :---------------------------: | :----: | :-----: | 23 | | count | num of clouds(云的数目) | number | 10 | -------------------------------------------------------------------------------- /docs/fog.md: -------------------------------------------------------------------------------- 1 | ![meteors](https://user-images.githubusercontent.com/6839576/130325909-4cc4b47c-7eb3-4b3a-b643-ded36d627a9c.png) 2 | 3 | ## usage 4 | 5 | ```tsx 6 | import { Fog, useTheme, FogCamera } from 'threejs-weather' 7 | import { Canvas } from '@react-three/fiber' 8 | 9 | const Page = () => { 10 | const { bind } = useTheme({ type: 'fog', mode: 'day' }) 11 | return ( 12 | 15 | 16 | 17 | 18 | ) 19 | } 20 | ``` 21 | 22 | ## props 23 | 24 | | name | description | type | default | 25 | | :---: | :---------------------------: | :----: | :-----: | 26 | | url | custom building model url | string | | -------------------------------------------------------------------------------- /docs/haze.md: -------------------------------------------------------------------------------- 1 | ![meteors](https://user-images.githubusercontent.com/6839576/130325909-4cc4b47c-7eb3-4b3a-b643-ded36d627a9c.png) 2 | 3 | ## usage 4 | 5 | ```tsx 6 | import { Haze, useTheme } from 'threejs-weather' 7 | import { Canvas } from '@react-three/fiber' 8 | 9 | const Page = () => { 10 | const { bind } = useTheme({ type: 'haze', mode: 'day' }) 11 | return ( 12 | 15 | 16 | 17 | ) 18 | } 19 | ``` 20 | 21 | ## props 22 | 23 | | name | description | type | default | 24 | | :---: | :---------------------------: | :----: | :-----: | 25 | | count | num of line | number | 100 | 26 | | angle | angle of line | deg | -45 | -------------------------------------------------------------------------------- /docs/meteors.md: -------------------------------------------------------------------------------- 1 | ![meteors](https://user-images.githubusercontent.com/6839576/82881928-ed077b00-9f72-11ea-80c8-788bdbe7d38c.gif) 2 | 3 | ## usage 4 | 5 | ```tsx 6 | import { Meteors, useTheme } from 'threejs-weather' 7 | import { Canvas } from 'react-three/fiber' 8 | 9 | const Page = () => { 10 | const { bind } = useTheme({ type: 'meteors', mode: 'day' }) 11 | return ( 12 | 15 | 16 | 17 | ) 18 | } 19 | ``` 20 | 21 | ## props 22 | 23 | | name | description | type | default | 24 | | :---: | :----------------------: | :----: | :-----: | 25 | | count | num of meteors(流星数目) | number | 30 | -------------------------------------------------------------------------------- /docs/rain-rings.md: -------------------------------------------------------------------------------- 1 | ![rain](https://user-images.githubusercontent.com/6839576/83318117-40741480-a264-11ea-9f28-e4e4b55326dd.gif) 2 | 3 | ## usage 4 | > 只会在特定角度出现 5 | 6 | ```tsx 7 | import { Rain, RainRings, useTheme } from 'threejs-weather' 8 | import { Canvas } from 'react-three/fiber' 9 | 10 | const RainPage = () => { 11 | const { bind } = useTheme({ type: 'rain', mode: 'day' }) 12 | return ( 13 | 16 | 17 | 18 | 19 | ) 20 | } 21 | ``` 22 | 23 | 可配合[rain-ring](/docs/rain-rings.md)使用 24 | 25 | ## props 26 | 27 | | name | description | type | default | 28 | | :---: | :---------------------------: | :----: | :-----: | 29 | | count | num of rainring(雨斑的数目) | number | 10 | -------------------------------------------------------------------------------- /docs/rain.md: -------------------------------------------------------------------------------- 1 | ![rain](https://user-images.githubusercontent.com/6839576/83318117-40741480-a264-11ea-9f28-e4e4b55326dd.gif) 2 | 3 | ## usage 4 | 5 | ```tsx 6 | import { Rain, useTheme } from 'threejs-weather' 7 | import { Canvas } from 'react-three/fiber' 8 | 9 | const RainPage = () => { 10 | const { bind } = useTheme({ type: 'rain', mode: 'day' }) 11 | return ( 12 | 15 | 16 | 17 | ) 18 | } 19 | ``` 20 | 21 | ## props 22 | 23 | | name | description | type | default | 24 | | :---: | :---------------------------: | :----: | :-----: | 25 | | count | num of raindrop(雨点的数目) | number | 100 | 26 | | angle | angle of raindrop(下雨的角度) | deg | -45 | -------------------------------------------------------------------------------- /docs/snow.md: -------------------------------------------------------------------------------- 1 | ![snow](https://user-images.githubusercontent.com/6839576/82968936-f7705600-a000-11ea-89ba-b33ed5d7bc77.gif) 2 | 3 | ## usage 4 | 5 | ```tsx 6 | import { Snow, useTheme } from 'threejs-weather' 7 | import { Canvas } from 'react-three/fiber' 8 | 9 | const CloudyPage = () => { 10 | const { bind } = useTheme({ type: 'snow', mode: 'day' }) 11 | return ( 12 | 13 | 14 | 15 | ) 16 | } 17 | ``` 18 | 19 | ## props 20 | 21 | | name | description | type | default | 22 | | :---: | :---------------------------: | :----: | :-----: | 23 | | count | num of snowflakes(雪花的数目) | number | 100 | -------------------------------------------------------------------------------- /docs/star-rings.md: -------------------------------------------------------------------------------- 1 | ![star-rings](https://user-images.githubusercontent.com/6839576/82881937-f0026b80-9f72-11ea-9cf2-fe2dd3f06937.gif) 2 | 3 | ## usage 4 | 5 | ```tsx 6 | import { StarRings, useTheme } from 'threejs-weather' 7 | import { Canvas } from 'react-three/fiber' 8 | 9 | const RainPage = () => { 10 | const { bind } = useTheme({ type: 'snow', mode: 'day' }) 11 | return ( 12 | 15 | 16 | 17 | ) 18 | } 19 | ``` 20 | 21 | ## props 22 | 23 | | name | description | type | default | 24 | | :---: | :--------------------: | :----: | :-----: | 25 | | count | num of rings(星环数目) | number | 50 | -------------------------------------------------------------------------------- /docs/sun.md: -------------------------------------------------------------------------------- 1 | ![sun](https://user-images.githubusercontent.com/6839576/82881946-f2fd5c00-9f72-11ea-8083-69b4dabd71d5.gif) 2 | 3 | ## usage 4 | 5 | ```tsx 6 | import { Sun, useTheme } from 'threejs-weather' 7 | import { Canvas } from 'react-three/fiber' 8 | 9 | const RainPage = () => { 10 | const { bind } = useTheme({ type: 'sun', mode: 'day' }) 11 | return ( 12 | 15 | 16 | 17 | ) 18 | } 19 | ``` 20 | 21 | ## props 22 | 23 | | name | description | type | default | 24 | | :------: | :------------------------------------: | :---------: | :-----: | 25 | | percentX | startpoint offset(太阳初始位置百分比)) | number(0-1) | 1 | 26 | | count | sum of halo(光晕数目)|number | 6 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | "> 1%" 9 | ] 10 | }, 11 | "useBuiltIns": "usage", 12 | "corejs": 3, 13 | "modules": "commonjs" 14 | } 15 | ], 16 | "@babel/preset-react" 17 | ], 18 | "plugins": [ 19 | "@babel/plugin-syntax-dynamic-import", 20 | "react-hot-loader/babel" 21 | ] 22 | } -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # common 2 | node_modules 3 | 4 | # editor 5 | .DS_Store 6 | .vscode 7 | 8 | # test 9 | converage 10 | cache 11 | jest 12 | 13 | # build 14 | dist 15 | lib 16 | .lib 17 | 18 | # log 19 | lerna-debug.log 20 | 21 | # types 22 | **/*.styl.d.ts 23 | 24 | # bin-template 25 | lib 26 | .lib 27 | 28 | # react-components-lib-tempate 29 | es 30 | -------------------------------------------------------------------------------- /example/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # example-for-threejs-weather 2 | 3 | ## 0.0.1 4 | ### Patch Changes 5 | 6 | - Updated dependencies [f2d2313] 7 | - Updated dependencies [381a5fa] 8 | - Updated dependencies [e71ca73] 9 | - Updated dependencies [cae159e] 10 | - Updated dependencies [ea560bb] 11 | - threejs-weather@1.0.0 12 | -------------------------------------------------------------------------------- /example/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 JW 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/build/config.js: -------------------------------------------------------------------------------- 1 | // NOTE: dont move this file 2 | const path = require('path') 3 | const Rupture = require('rupture') 4 | 5 | // path 6 | const context = path.resolve(__dirname, '../') 7 | const assets = path.resolve(context, 'src/assets') 8 | const project = path.resolve(context, 'src') 9 | const static = path.resolve(context, 'static') 10 | const output = path.resolve(context, 'dist') 11 | const public = path.resolve(context, 'public') 12 | 13 | const common = { 14 | path: { 15 | static, 16 | assets, 17 | project, 18 | output, 19 | context, 20 | public, 21 | tsconfig: path.resolve(context, 'tsconfig.json'), 22 | }, 23 | stylus: { 24 | plugins: [Rupture()], 25 | }, 26 | gzip: false, 27 | analyzer: false, 28 | workerpool: { 29 | workers: require('os').cpus().length - 1, 30 | poolTimeout: process.env.NODE_ENV === 'development' ? Infinity : 2000, 31 | }, 32 | } 33 | 34 | module.exports = common 35 | -------------------------------------------------------------------------------- /example/build/webpack.common.config.js: -------------------------------------------------------------------------------- 1 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin') 2 | const CopyWebpakcPlugin = require('copy-webpack-plugin') 3 | 4 | const configs = require('./config') 5 | 6 | /** 7 | * @type import('webpack').Configuration 8 | */ 9 | const common = { 10 | context: configs.path.context, 11 | entry: ['react-hot-loader/patch', './src/index.tsx'], 12 | output: { 13 | path: configs.path.output, 14 | filename: '[name].js', 15 | }, 16 | resolve: { 17 | extensions: ['.ts', '.tsx', '.js', 'jsx'], 18 | alias: { 19 | '@': configs.path.project, 20 | assets: configs.path.assets, 21 | static: configs.path.static, 22 | }, 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.tsx?$/, 28 | exclude: /node_modules/, 29 | use: [ 30 | { loader: 'cache-loader' }, 31 | { 32 | loader: 'thread-loader', 33 | options: configs.workerpool, 34 | }, 35 | { loader: 'babel-loader' }, 36 | { 37 | loader: 'ts-loader', 38 | options: { 39 | // IMPORTANT! use happyPackMode mode to speed-up compilation and reduce errors reported to webpack 40 | transpileOnly: true, 41 | happyPackMode: true, 42 | }, 43 | }, 44 | ], 45 | }, 46 | { 47 | test: /\.(png|jpg|gif)$/i, 48 | use: [ 49 | { 50 | loader: 'url-loader', 51 | options: { 52 | name: '[path][name].[ext]', 53 | }, 54 | }, 55 | ], 56 | }, 57 | ], 58 | }, 59 | plugins: [ 60 | new CopyWebpakcPlugin([ 61 | { 62 | from: configs.path.static, 63 | to: 'static', 64 | }, 65 | { 66 | from: configs.path.public, 67 | to: '', 68 | }, 69 | ]), 70 | new ForkTsCheckerWebpackPlugin({ 71 | tsconfig: configs.path.tsconfig, 72 | checkSyntacticErrors: true, 73 | }), 74 | ], 75 | } 76 | 77 | module.exports = common 78 | -------------------------------------------------------------------------------- /example/build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin') 2 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 3 | const MergeWebpack = require('webpack-merge') 4 | const ThreadLoader = require('thread-loader') 5 | const webpack = require('webpack') 6 | 7 | const configs = require('./config') 8 | const common = require('./webpack.common.config') 9 | const port = 8080 10 | 11 | ThreadLoader.warmup(configs.workerpool, ['ts-loader', 'babel-loader']) 12 | 13 | /** 14 | * @type import('webpack').Configuration 15 | */ 16 | const dev = { 17 | devtool: 'cheap-module-eval-source-map', 18 | mode: 'development', 19 | output: { 20 | path: configs.path.output, 21 | filename: '[name].js', 22 | publicPath: '/', 23 | }, 24 | resolve: { 25 | alias: { 26 | react: require.resolve('../../node_modules/react'), 27 | '@react-three/fiber': require.resolve('../../node_modules/@react-three/fiber'), 28 | }, 29 | }, 30 | devServer: { 31 | port, 32 | watchContentBase: true, 33 | contentBase: configs.path.public, 34 | hot: true, 35 | inline: true, 36 | overlay: true, 37 | compress: true, 38 | clientLogLevel: 'none', 39 | quiet: true, 40 | public: `http://localhost:${port}`, 41 | proxy: { 42 | '/proxy': { 43 | target: 'http://localhost:3000/', 44 | changeOrigin: true, 45 | }, 46 | }, 47 | }, 48 | module: { 49 | rules: [ 50 | { 51 | test: /\.css$/, 52 | use: [ 53 | { loader: 'style-loader', options: { sourceMap: true } }, 54 | { 55 | loader: 'typings-for-css-modules-loader', 56 | options: { 57 | sourceMap: true, 58 | modules: true, 59 | localIdentName: '[name]_[local]___[hash:base64:5]', 60 | namedExport: true, 61 | silent: true, 62 | }, 63 | }, 64 | ], 65 | }, 66 | { 67 | test: /(\.styl$|\.stylus$)/, 68 | use: [ 69 | { loader: 'style-loader', options: { sourceMap: true } }, 70 | { 71 | loader: 'typings-for-css-modules-loader', 72 | options: { 73 | sourceMap: true, 74 | modules: true, 75 | localIdentName: '[name]_[local]___[hash:base64:5]', 76 | namedExport: true, 77 | silent: true, 78 | }, 79 | }, 80 | { loader: 'postcss-loader', options: { sourceMap: true } }, 81 | { 82 | loader: 'stylus-loader', 83 | options: { 84 | sourceMap: true, 85 | use: configs.stylus.plugins, 86 | }, 87 | }, 88 | ], 89 | }, 90 | ], 91 | }, 92 | plugins: [ 93 | new HtmlWebpackPlugin({ 94 | filename: 'index.html', 95 | template: 'public/index.html', 96 | inject: true, 97 | }), 98 | new webpack.HotModuleReplacementPlugin(), 99 | new webpack.NamedModulesPlugin(), 100 | new FriendlyErrorsPlugin({ 101 | compilationSuccessInfo: { 102 | messages: [`Running here http://localhost:${port}`], 103 | notes: ['Happy coding'], 104 | }, 105 | onErrors(_severity, _errors) { 106 | // You can listen to errors transformed and prioritized by the plugin 107 | // severity can be 'error' or 'warning' 108 | }, 109 | }), 110 | ], 111 | } 112 | 113 | module.exports = MergeWebpack(common, dev) 114 | -------------------------------------------------------------------------------- /example/build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') 3 | const TerserPlugin = require('terser-webpack-plugin') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | const MiniCSSExtractPlugin = require('mini-css-extract-plugin') 6 | const CleanWebpackPlugin = require('clean-webpack-plugin') 7 | const MergeWebpack = require('webpack-merge') 8 | const PreloadWebpackPlugin = require('preload-webpack-plugin') 9 | const webpack = require('webpack') 10 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 11 | const CompressionPlugin = require('compression-webpack-plugin') 12 | 13 | const configs = require('./config') 14 | const common = require('./webpack.common.config') 15 | 16 | /** 17 | * @type import('webpack').Configuration 18 | */ 19 | const prod = { 20 | devtool: 'source-map', 21 | mode: 'production', 22 | output: { 23 | path: configs.path.output, 24 | filename: path.posix.join('static', 'js/[name].[chunkhash].js'), 25 | chunkFilename: path.posix.join('static', 'js/[name].[chunkhash].async.js'), 26 | publicPath: './', 27 | }, 28 | optimization: { 29 | splitChunks: { 30 | cacheGroups: { 31 | vendors: { 32 | test(module) { 33 | return module.resource && /react/.test(module.resource) 34 | }, 35 | name: 'vendors', 36 | chunks: 'all', 37 | priority: -10, 38 | }, 39 | commons: { 40 | chunks: 'async', 41 | name: 'async', 42 | minChunks: 2, 43 | minSize: 0, 44 | }, 45 | }, 46 | }, 47 | minimizer: [ 48 | new TerserPlugin({ 49 | parallel: true, 50 | extractComments: false, 51 | terserOptions: { 52 | warnings: false, 53 | compress: { 54 | drop_console: true, 55 | }, 56 | }, 57 | }), 58 | new OptimizeCssAssetsPlugin({ 59 | cssProcessorOptions: { 60 | safe: true, 61 | autoprefixer: { disable: true }, 62 | discardComments: { removeAll: true }, 63 | }, 64 | }), 65 | ], 66 | }, 67 | module: { 68 | rules: [ 69 | { 70 | test: /\.css$/, 71 | exclude: /node_modules/, 72 | use: [ 73 | { loader: MiniCSSExtractPlugin.loader, options: { sourceMap: true } }, 74 | { loader: 'css-loader', options: { sourceMap: true } }, 75 | { loader: 'postcss-loader', options: { sourceMap: true } }, 76 | ], 77 | }, 78 | { 79 | test: /(\.styl$|\.stylus$)/, 80 | use: [ 81 | { loader: MiniCSSExtractPlugin.loader, options: { sourceMap: true } }, 82 | { 83 | loader: 'css-loader', 84 | options: { 85 | sourceMap: true, 86 | modules: true, 87 | localIdentName: '[name]_[local]___[hash:base64:5]', 88 | }, 89 | }, 90 | { loader: 'postcss-loader', options: { sourceMap: true } }, 91 | { 92 | loader: 'stylus-loader', 93 | options: { 94 | sourceMap: true, 95 | use: configs.stylus.plugins, 96 | }, 97 | }, 98 | ], 99 | }, 100 | ], 101 | }, 102 | plugins: [ 103 | new CleanWebpackPlugin(), 104 | new webpack.HashedModuleIdsPlugin(), 105 | new HtmlWebpackPlugin({ 106 | filename: 'index.html', 107 | template: 'public/index.html', 108 | inject: true, 109 | minify: { 110 | collapseWhitespace: true, 111 | removeComments: true, 112 | removeEmptyAttributes: true, 113 | }, 114 | }), 115 | new PreloadWebpackPlugin({ 116 | rel: 'preload', 117 | include: ['vendors', 'main'], 118 | }), 119 | new MiniCSSExtractPlugin({ 120 | filename: path.posix.join('static', 'css/[name].[contenthash].css'), 121 | chunkFilename: path.posix.join('static', 'css/[name].[contenthash].async.css'), 122 | }), 123 | ] 124 | .concat( 125 | configs.analyzer 126 | ? [ 127 | new BundleAnalyzerPlugin({ 128 | openAnalyzer: false, 129 | }), 130 | ] 131 | : [], 132 | ) 133 | .concat(configs.gzip ? [new CompressionPlugin()] : []), 134 | } 135 | 136 | module.exports = MergeWebpack(common, prod) 137 | -------------------------------------------------------------------------------- /example/mock/fake.js: -------------------------------------------------------------------------------- 1 | const proxy = { 2 | 'GET /proxy/fake': (req, res) => { 3 | res.json( 4 | Array(10) 5 | .fill(0) 6 | .map((_v, i) => i), 7 | ) 8 | }, 9 | } 10 | 11 | export default proxy 12 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-for-threejs-weather", 3 | "version": "0.0.1", 4 | "description": "example-for-threejs-weather", 5 | "license": "MIT", 6 | "homepage": "https://github.com/JiangWeixian/templates#readme", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/JiangWeixian/templates.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/JiangWeixian/templates/issues" 13 | }, 14 | "author": "jiangwei", 15 | "main": "src/index.tsx", 16 | "scripts": { 17 | "test": "jest --config ./test/jest.config.js", 18 | "dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.config.js --host 0.0.0.0", 19 | "build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.config.js --progress", 20 | "update": "npm update && npm update --save-dev", 21 | "check": "npm outdated && npm outdated --save-dev", 22 | "mock": "PORT=3000 umi-serve", 23 | "prepublishOnly": "np --no-cleanup --yolo --no-publish --any-branch" 24 | }, 25 | "dependencies": { 26 | "@react-spring/web": "^9.2.4", 27 | "@react-three/drei": "^7.6.1", 28 | "@react-three/fiber": "^7.0.6", 29 | "axios": "0.21.1", 30 | "classnames": "2.3.1", 31 | "core-js": "3.16.2", 32 | "history": "5.0.1", 33 | "leva": "^0.9.13", 34 | "react": "17.0.2", 35 | "react-dom": "17.0.2", 36 | "react-router": "5.2.0", 37 | "react-router-dom": "5.2.0", 38 | "react-transition-group": "4.4.2", 39 | "require-context": "1.1.0", 40 | "styled-components": "5.3.0", 41 | "swr": "0.5.6", 42 | "three": "0.125.2", 43 | "threejs-meshline": "2.0.12", 44 | "threejs-weather": "^1.0.0", 45 | "wouter": "^2.7.4" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "7.15.0", 49 | "@babel/plugin-syntax-dynamic-import": "7.8.3", 50 | "@babel/preset-env": "7.15.0", 51 | "@babel/preset-react": "7.14.5", 52 | "@babel/preset-typescript": "7.15.0", 53 | "@types/chai": "4.1.7", 54 | "@types/classnames": "2.2.7", 55 | "@types/copy-webpack-plugin": "5.0.0", 56 | "@types/enzyme": "3.9.1", 57 | "@types/html-webpack-plugin": "3.2.0", 58 | "@types/jest": "24.0.12", 59 | "@types/lodash": "4.14.172", 60 | "@types/mini-css-extract-plugin": "0.2.0", 61 | "@types/optimize-css-assets-webpack-plugin": "1.3.4", 62 | "@types/react-dom": "17.0.9", 63 | "@types/react-hot-loader": "4.1.0", 64 | "@types/react-loadable": "5.5.6", 65 | "@types/react-router-dom": "5.1.8", 66 | "@types/styled-components": "5.1.12", 67 | "@types/uglifyjs-webpack-plugin": "1.1.0", 68 | "@types/webpack": "4.4.31", 69 | "@types/webpack-bundle-analyzer": "2.13.1", 70 | "@types/webpack-dev-server": "3.9.0", 71 | "@types/webpack-env": "1.13.9", 72 | "@types/webpack-merge": "4.1.5", 73 | "babel-core": "7.0.0-bridge.0", 74 | "babel-jest": "24.8.0", 75 | "babel-loader": "8.2.2", 76 | "babel-plugin-import": "1.13.3", 77 | "cache-loader": "3.0.0", 78 | "chai": "4.2.0", 79 | "clean-webpack-plugin": "2.0.2", 80 | "compression-webpack-plugin": "3.0.1", 81 | "copy-webpack-plugin": "5.0.3", 82 | "cross-env": "7.0.3", 83 | "css-loader": "1.0.1", 84 | "cssnano": "4.1.10", 85 | "cssnano-preset-advanced": "4.0.7", 86 | "enzyme": "3.9.0", 87 | "enzyme-adapter-react-16": "1.13.0", 88 | "fork-ts-checker-webpack-plugin": "1.3.1", 89 | "friendly-errors-webpack-plugin": "1.7.0", 90 | "html-webpack-plugin": "3.2.0", 91 | "jest": "24.8.0", 92 | "jest-css-modules": "2.0.0", 93 | "jest-localstorage-mock": "2.4.0", 94 | "mini-css-extract-plugin": "0.6.0", 95 | "np": "7.5.0", 96 | "optimize-css-assets-webpack-plugin": "5.0.1", 97 | "postcss-cssnext": "3.1.0", 98 | "postcss-import": "14.0.2", 99 | "postcss-js": "3.0.3", 100 | "postcss-loader": "3.0.0", 101 | "postcss-preset-env": "6.7.0", 102 | "postcss-url": "10.1.3", 103 | "preload-webpack-plugin": "3.0.0-beta.4", 104 | "prettier": "2.3.2", 105 | "prettier-quick": "0.0.5", 106 | "pretty-quick": "2.0.1", 107 | "react-hot-loader": "4.13.0", 108 | "react-loadable": "5.5.0", 109 | "rucksack-css": "1.0.2", 110 | "rupture": "0.7.1", 111 | "style-loader": "0.23.1", 112 | "stylus": "0.54.5", 113 | "stylus-loader": "3.0.2", 114 | "stylus-supremacy": "2.12.7", 115 | "terser-webpack-plugin": "3.0.2", 116 | "thread-loader": "2.1.3", 117 | "ts-import-plugin": "1.5.5", 118 | "ts-jest": "24.0.2", 119 | "ts-loader": "6.0.0", 120 | "tslib": "2.3.1", 121 | "typescript": "4.3.5", 122 | "typings-for-css-modules-loader": "1.7.0", 123 | "umi-serve": "1.9.4", 124 | "url-loader": "1.1.2", 125 | "webpack": "4.23.1", 126 | "webpack-bundle-analyzer": "3.3.2", 127 | "webpack-cli": "3.3.2", 128 | "webpack-dev-server": "3.3.1", 129 | "webpack-merge": "4.2.1" 130 | }, 131 | "browserslits": [ 132 | "> 1%" 133 | ] 134 | } 135 | -------------------------------------------------------------------------------- /example/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-preset-env': {}, 4 | 'rucksack-css': {}, 5 | 'postcss-import': {}, 6 | 'postcss-url': {}, 7 | 'postcss-cssnext': { 8 | browsers: ['> 1%'], 9 | }, 10 | cssnano: { 11 | preset: 'advanced', 12 | autoprefixer: false, 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /example/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /example/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /example/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/apple-touch-icon.png -------------------------------------------------------------------------------- /example/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/favicon-16x16.png -------------------------------------------------------------------------------- /example/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/favicon-32x32.png -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @threejs-weather 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 | -------------------------------------------------------------------------------- /example/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, 6 | { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } 7 | ], 8 | "theme_color": "#ffffff", 9 | "background_color": "#ffffff", 10 | "display": "standalone" 11 | } 12 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { hot } from 'react-hot-loader/root' 2 | import React from 'react' 3 | import RouterViewer from '@/routes' 4 | import { createGlobalStyle } from 'styled-components' 5 | 6 | const GlobalStyle = createGlobalStyle` 7 | body { 8 | padding: 0px; 9 | margin: 0px; 10 | } 11 | #app { 12 | width: 100vw; 13 | height: 100vh; 14 | } 15 | @font-face { 16 | font-family: font; 17 | src: url("/static/font.ttf") format("truetype") 18 | } 19 | ` 20 | 21 | const App = () => { 22 | return ( 23 | <> 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | export default hot(App) 31 | -------------------------------------------------------------------------------- /example/src/api/fake.ts: -------------------------------------------------------------------------------- 1 | import { get } from './utils' 2 | 3 | export namespace Fake { 4 | export type Response = number 5 | } 6 | 7 | export const fake = { 8 | async list(skip?: number, limit?: number): Promise { 9 | return get('/fake', { skip, limit }) 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /example/src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { fake, Fake } from './fake' 2 | 3 | const api = { 4 | fake, 5 | } 6 | 7 | export { Fake } 8 | 9 | export default api 10 | -------------------------------------------------------------------------------- /example/src/api/utils.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | axios.defaults.withCredentials = true 4 | axios.defaults.baseURL = '/proxy' 5 | 6 | export const get = async (path: string, params: Q): Promise => { 7 | return axios 8 | .get(path, { 9 | params, 10 | }) 11 | .then((res) => res.data) 12 | } 13 | 14 | export const post = async (path: string, params: Q): Promise => { 15 | return axios.post(path, params).then((res) => res.data) 16 | } 17 | -------------------------------------------------------------------------------- /example/src/components/WeatherSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import styled from 'styled-components' 3 | import { useLocation } from 'wouter' 4 | import { useWeather, types } from 'threejs-weather' 5 | import { useSprings, animated, useSpring } from '@react-spring/web' 6 | 7 | import { PATHS } from '@/constants' 8 | 9 | const StyledWeatherSwitcher = styled(animated.ul)` 10 | display: flex; 11 | justify-content: center; 12 | position: absolute; 13 | z-index: 1000; 14 | padding: 0px; 15 | left: 0px; 16 | right: 0px; 17 | bottom: 0px; 18 | padding: 16px; 19 | text-align: center; 20 | font-family: font; 21 | font-size: 24px; 22 | 23 | li { 24 | list-style-type: none; 25 | padding: 0px 4px; 26 | cursor: pointer; 27 | } 28 | ` 29 | 30 | export const WeatherSwitcher = () => { 31 | const [location, setLocation] = useLocation() 32 | const values = Object.values(PATHS) 33 | const index = values.findIndex((v) => v.path === location) 34 | const [activeIndex, setActiveIndex] = useState(index < 0 ? 0 : index) 35 | const [springs, set] = useSprings(values.length, () => ({ 36 | opacity: 0.2, 37 | })) 38 | const color = useSpring({ 39 | color: PATHS[location.replace('/prod/', '')]?.mode === 'light' ? '#000' : '#fff', 40 | }) 41 | const { handleChangeType } = useWeather() 42 | useEffect(() => { 43 | set(((index: number) => { 44 | if (index !== activeIndex) { 45 | return { opacity: 0.2 } 46 | } 47 | return { opacity: 1 } 48 | }) as any) 49 | }, [activeIndex, set]) 50 | return ( 51 | 52 | {springs.map((props, i) => { 53 | return ( 54 | { 58 | setActiveIndex(i) 59 | handleChangeType?.(values[i].path.replace('/prod/', '') as types.Weather) 60 | setLocation(values[i].path) 61 | }} 62 | onMouseEnter={() => { 63 | set(((index: number) => { 64 | if (index !== i && index !== activeIndex) { 65 | return { opacity: 0.2 } 66 | } 67 | return { opacity: 1 } 68 | }) as any) 69 | }} 70 | onMouseLeave={() => { 71 | set(((index: number) => { 72 | if (index === i && index !== activeIndex) { 73 | return { opacity: 0.2 } 74 | } 75 | }) as any) 76 | }} 77 | > 78 | {values[i].name} 79 | 80 | ) 81 | })} 82 | 83 | ) 84 | } 85 | -------------------------------------------------------------------------------- /example/src/components/WeatherText.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { useFrame, useThree } from '@react-three/fiber' 3 | import { Mesh, Vector3, Color } from 'three' 4 | import { Text } from '@react-three/drei' 5 | 6 | type TextProps = { 7 | children: string 8 | color?: string 9 | position?: Vector3 10 | style?: any 11 | } 12 | 13 | const InnerText = Text as any 14 | 15 | export const WeatherText = ({ color = '#310f1b', ...props }: TextProps) => { 16 | const text = useRef() 17 | const { camera } = useThree() 18 | useFrame(() => { 19 | if (!text.current || !camera) { 20 | return 21 | } 22 | const rotation = camera.rotation 23 | text.current.rotation.x = rotation.x 24 | text.current.rotation.y = rotation.y 25 | text.current.rotation.z = rotation.z 26 | if (Array.isArray(text.current.material)) { 27 | text.current.material.forEach((v) => { 28 | v.opacity = props.style?.opacity.get() ?? 1 29 | ;(v as any).color = new Color(color) 30 | }) 31 | } else { 32 | text.current.material.opacity = props.style?.opacity.get() ?? 1 33 | ;(text.current.material as any).color = new Color(color) 34 | } 35 | }) 36 | return ( 37 | 45 | 46 | {props.children} 47 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /example/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const PATHS = { 2 | rain: { 3 | name: '雨', 4 | path: '/prod/rain', 5 | count: 100, 6 | }, 7 | cloudy: { 8 | name: '多云', 9 | path: '/prod/cloudy', 10 | count: 10, 11 | }, 12 | partlyCloudy: { 13 | name: '阴天', 14 | path: '/prod/partly-cloudy', 15 | count: 10, 16 | }, 17 | sun: { 18 | name: '晴', 19 | path: '/prod/sun', 20 | mode: 'light', 21 | count: 6, 22 | }, 23 | snow: { 24 | name: '雪', 25 | path: '/prod/snow', 26 | count: 10, 27 | }, 28 | meteors: { 29 | name: '流星', 30 | path: '/prod/meteors', 31 | count: 10, 32 | }, 33 | starRings: { 34 | name: '星环', 35 | path: '/prod/star-rings', 36 | count: 100, 37 | }, 38 | fog: { 39 | name: '雾', 40 | path: '/prod/fog', 41 | count: 10, 42 | }, 43 | haze: { 44 | name: '霾', 45 | path: '/prod/haze', 46 | count: 10, 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import App from './App' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | 5 | const $ROOT = document.querySelector('#app') 6 | 7 | const renderApp = (Component: any) => { 8 | render(, $ROOT) 9 | } 10 | 11 | document.addEventListener('DOMContentLoaded', () => { 12 | renderApp(App) 13 | }) 14 | -------------------------------------------------------------------------------- /example/src/pages/prod/cloudy.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats } from '@react-three/drei' 3 | 4 | import { Cloudy } from 'threejs-weather' 5 | import { WeatherText } from '@/components/WeatherText' 6 | import { PATHS } from '@/constants' 7 | 8 | const CloudyPage = (props) => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | 16 | {PATHS.cloudy.name} 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default CloudyPage 24 | -------------------------------------------------------------------------------- /example/src/pages/prod/fog.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats } from '@react-three/drei' 3 | import { Vector3 } from 'three' 4 | 5 | import { Fog } from 'threejs-weather' 6 | import { WeatherText } from '@/components/WeatherText' 7 | import { PATHS } from '@/constants' 8 | 9 | const position = [127.45293777867074, 62.11080512264083, 137.6247069251716] 10 | 11 | const FogPage = (props) => { 12 | return ( 13 | <> 14 | 15 | 20 | {PATHS.fog.name} 21 | 22 | 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | export default FogPage 30 | -------------------------------------------------------------------------------- /example/src/pages/prod/haze.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats, OrbitControls } from '@react-three/drei' 3 | import { Haze } from 'threejs-weather' 4 | 5 | import { WeatherText } from '@/components/WeatherText' 6 | import { PATHS } from '@/constants' 7 | 8 | const OC: any = OrbitControls 9 | 10 | const HazePage = (props) => { 11 | return ( 12 | <> 13 | 14 | 15 | 16 | {PATHS.haze.name} 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | export default HazePage 26 | -------------------------------------------------------------------------------- /example/src/pages/prod/meteors.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats } from '@react-three/drei' 3 | 4 | import { Meteors } from 'threejs-weather' 5 | import { WeatherText } from '@/components/WeatherText' 6 | import { PATHS } from '@/constants' 7 | 8 | const MeteorsPage = (props) => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | {PATHS.meteors.name} 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default MeteorsPage 23 | -------------------------------------------------------------------------------- /example/src/pages/prod/partly-cloudy.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats } from '@react-three/drei' 3 | import { PartlyCloudy } from 'threejs-weather' 4 | 5 | import { WeatherText } from '@/components/WeatherText' 6 | import { PATHS } from '@/constants' 7 | 8 | const PartlyCloudyPage = (props) => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | {PATHS.partlyCloudy.name} 16 | 17 | 18 | {/* */} 19 | 20 | ) 21 | } 22 | 23 | export default PartlyCloudyPage 24 | -------------------------------------------------------------------------------- /example/src/pages/prod/rain.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats, OrbitControls } from '@react-three/drei' 3 | 4 | import { Rain, RainRing } from 'threejs-weather' 5 | import { WeatherText } from '@/components/WeatherText' 6 | import { PATHS } from '@/constants' 7 | 8 | const OC: any = OrbitControls 9 | 10 | const RainPage = (props) => { 11 | return ( 12 | <> 13 | 14 | 15 | 16 | 17 | 18 | 19 | {PATHS.rain.name} 20 | 21 | 22 | 23 | ) 24 | } 25 | 26 | export default RainPage 27 | -------------------------------------------------------------------------------- /example/src/pages/prod/snow.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats } from '@react-three/drei' 3 | import { Snow } from 'threejs-weather' 4 | 5 | import { WeatherText } from '@/components/WeatherText' 6 | import { PATHS } from '@/constants' 7 | 8 | const SnowPage = (props) => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | {PATHS.snow.name} 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default SnowPage 23 | -------------------------------------------------------------------------------- /example/src/pages/prod/star-ring.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats } from '@react-three/drei' 3 | 4 | import { StarRings } from 'threejs-weather' 5 | import { WeatherText } from '@/components/WeatherText' 6 | import { PATHS } from '@/constants' 7 | 8 | const StarRingsPage = (props) => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | {PATHS.starRings.name} 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default StarRingsPage 23 | -------------------------------------------------------------------------------- /example/src/pages/prod/sun.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Stats } from '@react-three/drei' 3 | 4 | import { Sun } from 'threejs-weather' 5 | import { WeatherText } from '@/components/WeatherText' 6 | import { PATHS } from '@/constants' 7 | 8 | const SunPage = (props) => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | {PATHS.sun.name} 15 | 16 | 17 | ) 18 | } 19 | 20 | export default SunPage 21 | -------------------------------------------------------------------------------- /example/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useEffect, useState, useCallback } from 'react' 2 | import { useLocation, Switch, Route, Router } from 'wouter' 3 | import { useTransition, WeatherProvider, types } from 'threejs-weather' 4 | import { a } from '@react-spring/three' 5 | import { Leva, useControls } from 'leva' 6 | import Sun from '@/pages/prod/sun' 7 | import Cloudy from '@/pages/prod/cloudy' 8 | import StarRing from '@/pages/prod/star-ring' 9 | import Snow from '@/pages/prod/snow' 10 | import Rain from '@/pages/prod/rain' 11 | import Meteors from '@/pages/prod/meteors' 12 | import Haze from '@/pages/prod/haze' 13 | import Fog from '@/pages/prod/fog' 14 | import PartlyCloudy from '@/pages/prod/partly-cloudy' 15 | 16 | import { PATHS } from '@/constants' 17 | import { WeatherSwitcher } from '@/components/WeatherSwitcher' 18 | import { getWeatherType } from '@/utils/weather' 19 | 20 | const Transition = () => { 21 | const [location] = useLocation() 22 | const defaultCount = useMemo(() => { 23 | const path = Object.values(PATHS).find((item) => item.path === location) 24 | return path?.count || 6 25 | }, [location]) 26 | const [{ count }, set] = useControls(() => ({ 27 | count: { 28 | value: defaultCount, 29 | max: 100, 30 | min: 1, 31 | }, 32 | })) 33 | useEffect(() => { 34 | set({ count: defaultCount }) 35 | }, [defaultCount, set]) 36 | console.log(location) 37 | const { transition } = useTransition({ location }) 38 | const type = location === '/' ? 'rain' : getWeatherType(location) || 'rain' 39 | return ( 40 | }> 41 | {transition((style, _location, p) => { 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ) 75 | })} 76 | 77 | ) 78 | } 79 | 80 | const currentLoc = () => window.location.hash.replace('#', '') || '/' 81 | 82 | const useHashLocation = () => { 83 | const [loc, setLoc] = useState(currentLoc()) 84 | 85 | useEffect(() => { 86 | const handler = () => setLoc(currentLoc()) 87 | 88 | // subscribe on hash changes 89 | window.addEventListener('hashchange', handler) 90 | return () => window.removeEventListener('hashchange', handler) 91 | }, []) 92 | 93 | const navigate = useCallback((to) => (window.location.hash = to), []) 94 | 95 | useEffect(() => { 96 | if (loc === '/') { 97 | navigate(PATHS.rain.path) 98 | } 99 | }, [loc, navigate]) 100 | return [loc, navigate] 101 | } 102 | 103 | const HashRouter = (props) => { 104 | return {props.children} 105 | } 106 | 107 | const RouterViewer = () => { 108 | return ( 109 | 110 | 111 | 112 | 113 | ) 114 | } 115 | 116 | export default RouterViewer 117 | -------------------------------------------------------------------------------- /example/src/typings/component.d.ts: -------------------------------------------------------------------------------- 1 | export type Props = 2 | | (JSX.IntrinsicAttributes & 3 | React.PropsWithoutRef & 4 | // tslint:disable-next-line 5 | React.RefAttributes>) 6 | | (JSX.IntrinsicAttributes & React.PropsWithRef>) 7 | -------------------------------------------------------------------------------- /example/src/typings/rematch.d.ts: -------------------------------------------------------------------------------- 1 | import models from '@/store/models' 2 | import { RematchStore, RematchDispatch, RematchRootState } from '@rematch/core' 3 | import { ReactThreeFiber } from '@react-three/fiber' 4 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' 5 | 6 | type Models = typeof models 7 | 8 | export type Store = RematchStore 9 | export type Dispatch = RematchDispatch 10 | export type RootState = RematchRootState 11 | 12 | declare global { 13 | namespace JSX { 14 | interface IntrinsicElements { 15 | orbitControls: ReactThreeFiber.Object3DNode 16 | meshLine: any 17 | meshLineMaterial: any 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/src/typings/type.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.styl' 2 | -------------------------------------------------------------------------------- /example/src/utils/weather.ts: -------------------------------------------------------------------------------- 1 | export const getWeatherType = (loc: string) => { 2 | return loc.replace('/prod/', '') 3 | } 4 | -------------------------------------------------------------------------------- /example/static/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/static/font.ttf -------------------------------------------------------------------------------- /example/test/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | verbose: true, 5 | collectCoverage: true, 6 | globals: { 7 | NODE_ENV: 'test', 8 | }, 9 | testEnvironment: 'jsdom', 10 | rootDir: path.resolve(__dirname, '../'), 11 | moduleFileExtensions: ['tsx', 'jsx', 'js', 'ts'], 12 | moduleNameMapper: { 13 | '^@/(.*)$': '/src/$1', 14 | }, 15 | transform: { 16 | '^.+\\.(js|jsx)$': '/node_modules/babel-jest', 17 | '.*\\.(ts|tsx)$': '/node_modules/ts-jest/preprocessor.js', 18 | '^.+\\.(css)$': '/node_modules/jest-css-modules', 19 | }, 20 | setupFiles: ['jest-localstorage-mock', '/test/jest.setup.js'], 21 | } 22 | -------------------------------------------------------------------------------- /example/test/jest.setup.js: -------------------------------------------------------------------------------- 1 | const enzyme = require('enzyme') 2 | const Adapter = require('enzyme-adapter-react-16') 3 | 4 | enzyme.configure({ adapter: new Adapter() }) 5 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "baseUrl": ".", 11 | "strict": true, 12 | "resolveJsonModule": true, 13 | "noImplicitAny": false, 14 | "skipLibCheck": true, 15 | "paths": { 16 | "@/*": ["src/*"] 17 | }, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | interface IntrinsicElements { 3 | orbitControls: ReactThreeFiber.Object3DNode 4 | meshLine: any 5 | meshLineMaterial: any 6 | } 7 | } 8 | 9 | declare global { 10 | namespace JSX { 11 | interface IntrinsicElements { 12 | orbitControls: ReactThreeFiber.Object3DNode 13 | meshLine: any 14 | meshLineMaterial: any 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const through2 = require('through2') 3 | const ts = require('gulp-typescript') 4 | const babel = require('gulp-babel') 5 | const rimraf = require('rimraf') 6 | const merge2 = require('merge2') 7 | const gulp = require('gulp') 8 | const sourcemaps = require('gulp-sourcemaps') 9 | const tsDefaultReporter = ts.reporter.defaultReporter() 10 | 11 | // config 12 | const config = require('./build/gulp.config') 13 | const source = ['components/**/*.tsx', 'components/**/*.ts', 'typings/**/*.d.ts'] 14 | const tsProject = ts.createProject('./tsconfig.json') 15 | const lib = process.env.NODE_ENV === 'development' ? config.dirs.devLib : config.dirs.lib 16 | const es = process.env.NODE_ENV === 'development' ? config.dirs.devEs : config.dirs.es 17 | 18 | function babelify(js, modules) { 19 | const babelConfig = config.getBabelConfig(modules) 20 | delete babelConfig.cacheDirectory 21 | const stream = js 22 | .pipe(sourcemaps.init()) 23 | .pipe(babel(babelConfig)) 24 | .pipe( 25 | through2 26 | .obj(function z(file, encoding, next) { 27 | this.push(file.clone()) 28 | next() 29 | }) 30 | .pipe(sourcemaps.write('.')), 31 | ) 32 | return stream.pipe(gulp.dest(modules === false ? es : lib)) 33 | } 34 | 35 | function compile(modules) { 36 | rimraf.sync(modules !== false ? lib : es) 37 | const assets = gulp 38 | .src(['./components/**/*.@(png|svg)']) 39 | .pipe(gulp.dest(modules === false ? es : lib)) 40 | let error = 0 41 | // allow jsx file in src/xxx/ 42 | if (config.tsConfig.allowJs) { 43 | source.unshift('components/**/*.jsx') 44 | } 45 | const tsResult = tsProject.src().pipe( 46 | ts(config.tsConfig, { 47 | finish: tsDefaultReporter.finish, 48 | }).on('error', () => {}), 49 | ) 50 | 51 | const tsFilesStream = babelify(tsResult.js, modules) 52 | const tsd = tsResult.dts.pipe(gulp.dest(modules === false ? es : lib)) 53 | return merge2([tsFilesStream, tsd, assets]) 54 | } 55 | 56 | gulp.task('compile-with-es', (done) => { 57 | console.log('[Parallel] Compile to es...') 58 | compile(false).on('finish', done) 59 | }) 60 | 61 | gulp.task('compile-with-lib', (done) => { 62 | console.log('[Parallel] Compile to lib...') 63 | compile().on('finish', done) 64 | }) 65 | 66 | gulp.task('compile', gulp.series(gulp.parallel('compile-with-es', 'compile-with-lib'))) 67 | gulp.task('watch', () => { 68 | gulp.watch(source, gulp.series(gulp.parallel('compile-with-es', 'compile-with-lib'))) 69 | }) 70 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cacheDirectory: './jest/cache', 3 | collectCoverage: true, 4 | collectCoverageFrom: ['src/**/*'], 5 | coverageDirectory: './jest/coverage', 6 | preset: 'ts-jest', 7 | resetMocks: true, 8 | resetModules: true, 9 | restoreMocks: true, 10 | globals: { 11 | 'ts-jest': { 12 | diagnostics: false, 13 | }, 14 | }, 15 | moduleNameMapper: { 16 | '@/(.*)': '/src/$1', 17 | }, 18 | roots: [''], 19 | moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'], 20 | testRegex: '/__test__/.+\\.test\\.tsx?$', 21 | verbose: false, 22 | setupFiles: ['/test/setupTests.ts'], 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threejs-weather", 3 | "version": "1.0.0", 4 | "description": "threejs-weather", 5 | "keywords": [ 6 | "react", 7 | "tree", 8 | "weather" 9 | ], 10 | "license": "MIT", 11 | "repository": { 12 | "url": "https://github.com/JiangWeixian/threejs-weather", 13 | "type": "git" 14 | }, 15 | "author": "JW", 16 | "files": [ 17 | "es", 18 | "lib" 19 | ], 20 | "main": "lib/index.js", 21 | "module": "es/index.js", 22 | "typings": "es/index.d.ts", 23 | "scripts": { 24 | "test": "cross-env NODE_ENV=test jest", 25 | "clean": "rimraf es && rimraf lib", 26 | "dev": "pnpm run dev --prefix example", 27 | "lint:fix": "eslint . --fix", 28 | "release": "pnpm run build && pnpm changeset publish", 29 | "build": "pnpm run clean && cross-env NODE_ENV=production gulp compile", 30 | "build:dev": "pnpm run clean && cross-env NODE_ENV=development gulp compile" 31 | }, 32 | "husky": { 33 | "hooks": { 34 | "pre-commit": "lint-staged" 35 | } 36 | }, 37 | "lint-staged": { 38 | "**/**/*.{js,ts,tsx,vue,json}": [ 39 | "eslint --fix" 40 | ] 41 | }, 42 | "peerDependencies": { 43 | "@react-three/fiber": ">=7.5.0", 44 | "three": ">=0.125.2" 45 | }, 46 | "dependencies": { 47 | "@react-spring/core": "^9.2.4", 48 | "@react-spring/three": "^9.2.4", 49 | "three.meshline": "^1.3.0", 50 | "threejs-meshline": "2.0.12" 51 | }, 52 | "devDependencies": { 53 | "@aiou/eslint-config": "^0.2.1", 54 | "@babel/core": "7.15.0", 55 | "@babel/plugin-transform-typescript": "7.15.0", 56 | "@babel/preset-env": "7.15.0", 57 | "@babel/preset-react": "7.14.5", 58 | "@changesets/cli": "^2.16.0", 59 | "@react-three/drei": "^7.6.1", 60 | "@react-three/fiber": "^7.0.6", 61 | "@testing-library/react-hooks": "3.2.1", 62 | "@types/classnames": "2.2.9", 63 | "@types/enzyme-adapter-react-16": "1.0.5", 64 | "@types/lodash.isnull": "3.0.6", 65 | "@types/react": "17.0.19", 66 | "@types/react-dom": "17.0.9", 67 | "autoprefixer": "10.3.2", 68 | "babel-plugin-import": "1.13.3", 69 | "core-js": "3.16.2", 70 | "cross-env": "7.0.3", 71 | "cz-emoji": "^1.3.1", 72 | "debug": "4.3.2", 73 | "enzyme": "3.10.0", 74 | "enzyme-adapter-react-16": "1.15.1", 75 | "eslint": "^7.32.0", 76 | "fs-extra": "10.0.0", 77 | "gulp": "4.0.2", 78 | "gulp-babel": "8.0.0", 79 | "gulp-replace": "1.1.3", 80 | "gulp-sourcemaps": "3.0.0", 81 | "gulp-typescript": "5.0.1", 82 | "husky": "~3.1.0", 83 | "jest": "24.9.0", 84 | "lint-staged": "^11.1.2", 85 | "lodash.assign": "4.2.0", 86 | "merge2": "1.4.1", 87 | "np": "5.0.3", 88 | "postcss-modules": "4.2.2", 89 | "poststylus": "1.0.1", 90 | "prettier": "2.3.2", 91 | "pretty-quick": "2.0.1", 92 | "react": "17.0.2", 93 | "react-dom": "17.0.2", 94 | "rimraf": "3.0.2", 95 | "three": "^0.125.2", 96 | "through2": "4.0.2", 97 | "ts-jest": "24.2.0", 98 | "ts-loader": "9.2.5", 99 | "tslib": "2.3.1", 100 | "typescript": "4.3.5" 101 | }, 102 | "config": { 103 | "commitizen": { 104 | "path": "cz-emoji" 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'example' 3 | - './' 4 | -------------------------------------------------------------------------------- /test/setupTests.ts: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme' 2 | import Adapter from 'enzyme-adapter-react-16' 3 | 4 | Enzyme.configure({ adapter: new Adapter() }) 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "experimentalDecorators": true, 8 | "jsx": "react", 9 | "noUnusedParameters": true, 10 | "noUnusedLocals": true, 11 | "noImplicitAny": true, 12 | "target": "ES2015", 13 | "skipLibCheck": true, 14 | "lib": ["dom", "es2017"], 15 | "resolveJsonModule": true, 16 | "rootDir": "./components", 17 | "outDir": "es" 18 | }, 19 | "include": ["components", "global.d.ts"], 20 | "exclude": ["node_modules", "lib", "es", "example"] 21 | } 22 | -------------------------------------------------------------------------------- /typings/style.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.styl' 2 | 3 | declare module '*.css' 4 | 5 | declare module '*.json' 6 | --------------------------------------------------------------------------------