Commit 03f08106 by zhuxichen

xx

parent a8d321bc
......@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from 'fs';
export default function customLoaderPlugin() {
return {
name: 'custom-loader-plugin',
transform(source, id) {
transform(source: string, id: string) {
// 替换路径1
const newPath = id.replace(/\/engines\/(.+?)\//, '/');
if (existsSync(newPath)) {
......
......@@ -16,7 +16,10 @@
"format": "prettier --write src/"
},
"dependencies": {
"@tweenjs/tween.js": "^25.0.0",
"@types/three": "^0.171.0",
"ant-design-vue": "^4.2.6",
"three": "^0.171.0",
"vue": "^3.5.13",
"vue-router": "^4.4.5"
},
......@@ -58,6 +61,7 @@
"unplugin-auto-import": "^0.18.6",
"unplugin-vue-components": "^0.27.5",
"vite": "^6.0.1",
"vite-plugin-prerender": "^1.0.8",
"vite-plugin-vue-devtools": "^7.6.5",
"vitest": "^2.1.5",
"vue-tsc": "^2.1.10"
......
// postcss.config.js
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
This source diff could not be displayed because it is too large. You can view the blob instead.
/* src/style.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
......@@ -12,6 +12,7 @@ import { updateThemeVariable } from '@/plugins/switchTheme';
<div class="vvv_color">测试主题stylus使用 $parimary-color</div>
<div class="vvv_color2">测试主题less使用 @warning-color</div>
<button @click="updateThemeVariable('primary-color', '#ff0000')">点击改变默认主题色</button>
<my-web-component name="Vue User"></my-web-component>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
......
......@@ -5,6 +5,7 @@ import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { initializeTheme } from './plugins/switchTheme';
import './assets/my-web-component.es.js';
const app = createApp(App);
// 读取默认主题模式
......
<template>
<div class="about">
<h1>This is an about page</h1>
<div class="vk relative">
<div class="absolute right-0 top-0 z-10 w-full h-full text-white" @click="changeSize">
改变大小{{ canvasSize }}
</div>
<PraticleEffect
imageUrl="/v_logo.png"
:canvasSize="900"
:perspective="500"
:particleSize="0.9"
:depthRange="depthRange"
:imageSize="canvasSize"
:particleStep="2"
:openChangeAnimation="true"
/>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
<script setup lang="ts">
import PraticleEffect from './Component/PraticleEffect.vue';
const canvasSize = ref(450);
const depthRange = ref(50);
const changeSize = () => {
canvasSize.value = canvasSize.value === 450 ? 200 : 300;
depthRange.value = depthRange.value === 30 ? 50 : 30;
};
</script>
<style scoped>
.vk {
width: 100vw;
height: 100vh;
}
</style>
<template>
<div class="particle-container">
<canvas ref="canvasRef"></canvas>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import * as THREE from 'three';
// 参数配置
const particleCount = 1000; // 粒子数量
const maxRange = 1000; // 粒子生成的坐标范围
const particleSize = 5; // 粒子大小
const rotationSpeed = 0.0005; // 背景粒子旋转速度
const layerDepth = [-500, -600, -700]; // Z轴上不同平面的深度
const canvasRef = ref<HTMLCanvasElement | null>(null);
let scene: THREE.Scene, camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer;
let xBgPoints: THREE.Points, yBgPoints: THREE.Points, zBgPoints: THREE.Points;
let animationId: number;
let pointTexture: THREE.Texture;
// 初始化场景
function initScene() {
const canvas = canvasRef.value;
if (!canvas) return;
// 场景
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0.0005); // 添加雾化效果
scene.background = new THREE.Color(0x0c0c18);
// 相机
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 3000);
camera.position.z = 1000;
// 渲染器
renderer = new THREE.WebGLRenderer({ canvas, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
// 纹理加载
const loader = new THREE.TextureLoader();
pointTexture = loader.load('/gradient.png'); // 替换为您自己的纹理路径
// 初始化粒子背景
initBackgroundPoints();
}
// 随机坐标生成
function randomPosition(min: number, max: number) {
return Math.random() * (max - min) + min;
}
// 初始化背景粒子
function initBackgroundPoints() {
const geometry = new THREE.BufferGeometry();
const positions = [];
const colors = [];
for (let i = 0; i < particleCount; i++) {
positions.push(
randomPosition(-maxRange, maxRange),
randomPosition(-maxRange, maxRange),
randomPosition(-maxRange, maxRange),
);
colors.push(1, 1, 1); // 白色
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: particleSize,
map: pointTexture,
blending: THREE.AdditiveBlending,
depthWrite: false,
transparent: true,
vertexColors: true,
});
// 创建三层粒子
xBgPoints = new THREE.Points(geometry, material);
xBgPoints.position.z = layerDepth[0];
yBgPoints = new THREE.Points(geometry, material);
yBgPoints.position.z = layerDepth[1];
zBgPoints = new THREE.Points(geometry, material);
zBgPoints.position.z = layerDepth[2];
scene.add(xBgPoints, yBgPoints, zBgPoints);
}
// 动画渲染逻辑
function animate() {
animationId = requestAnimationFrame(animate);
// 旋转粒子背景
xBgPoints.rotation.y += rotationSpeed;
yBgPoints.rotation.y -= rotationSpeed;
zBgPoints.rotation.z += rotationSpeed / 2;
renderer.render(scene, camera);
}
// 处理窗口大小变化
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 生命周期管理
onMounted(() => {
initScene();
animate();
window.addEventListener('resize', onResize);
});
onUnmounted(() => {
cancelAnimationFrame(animationId);
window.removeEventListener('resize', onResize);
});
</script>
<style scoped>
.particle-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000;
}
</style>
<template>
<div class="particle-container">
<canvas ref="canvasRef"></canvas>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import * as THREE from 'three';
// 参数配置
const pointCount = 800; // 粒子数量
const particleSize = 5; // 粒子大小
const maxRange = 1000; // 粒子生成范围
const rotationSpeed = 0.001; // 旋转速度
const canvasRef = ref<HTMLCanvasElement | null>(null);
let renderer: THREE.WebGLRenderer;
let scene: THREE.Scene;
let camera: THREE.PerspectiveCamera;
let animationId: number;
let xBgPoints: THREE.Points, yBgPoints: THREE.Points, zBgPoints: THREE.Points;
// 初始化场景
function initScene() {
const canvas = canvasRef.value;
if (!canvas) return;
const width = window.innerWidth;
const height = window.innerHeight;
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// 创建相机
camera = new THREE.PerspectiveCamera(40, width / height, 1, 3000);
camera.position.z = 1000;
// 创建渲染器
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
// 创建粒子背景
initBackgroundPoints();
}
// 随机生成坐标
function randomPosition(min: number, max: number) {
return Math.random() * (max - min) + min;
}
// 初始化背景粒子
function initBackgroundPoints() {
const geometry = new THREE.BufferGeometry();
const positions = [];
for (let i = 0; i < pointCount; i++) {
positions.push(
randomPosition(-maxRange, maxRange),
randomPosition(-maxRange, maxRange),
randomPosition(-maxRange, maxRange),
);
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({
size: particleSize,
color: new THREE.Color(0x00aaff), // 纯色粒子
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending,
});
// 创建三层粒子系统
xBgPoints = new THREE.Points(geometry, material);
xBgPoints.position.z = -0.5 * maxRange;
yBgPoints = new THREE.Points(geometry, material);
yBgPoints.position.z = -0.6 * maxRange;
zBgPoints = new THREE.Points(geometry, material);
zBgPoints.position.z = -0.7 * maxRange;
scene.add(xBgPoints);
scene.add(yBgPoints);
scene.add(zBgPoints);
}
// 动画循环
function animate() {
animationId = requestAnimationFrame(animate);
// 添加旋转效果
if (xBgPoints && yBgPoints && zBgPoints) {
xBgPoints.rotation.y += rotationSpeed;
yBgPoints.rotation.y += -rotationSpeed;
zBgPoints.rotation.z += rotationSpeed / 2;
}
renderer.render(scene, camera);
}
// 窗口大小自适应
function onResize() {
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
// 生命周期钩子
onMounted(() => {
initScene();
animate();
window.addEventListener('resize', onResize);
});
onUnmounted(() => {
cancelAnimationFrame(animationId);
window.removeEventListener('resize', onResize);
});
</script>
<style scoped>
.particle-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000;
}
</style>
<template>
<div
class="starfield-container"
:style="{
backgroundImage: computedBgImage,
backgroundColor: backgroundType === 'color' ? backgroundColor : 'transparent',
}"
>
<canvas ref="canvasRef"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
const props = defineProps({
/**
* 粒子数量,决定星空的密集程度
*/
particleCount: {
type: Number as PropType<number>,
default: 200,
},
/**
* 粒子飞行速度,数值越大速度越快
*/
speed: {
type: Number as PropType<number>,
default: 1.5,
},
/**
* 焦距 (Field of View),影响透视效果的强弱
*/
fov: {
type: Number as PropType<number>,
default: 300,
},
/**
* 粒子距离观察者的最大深度
*/
maxDepth: {
type: Number as PropType<number>,
default: 2000,
},
/**
* 粒子距离观察者的最小深度,避免粒子太靠近导致异常放大
*/
minDepth: {
type: Number as PropType<number>,
default: 250,
},
/**
* 粒子显示的图像路径,如果为空则使用默认渐变圆点
*/
imageSrc: {
type: String as PropType<string>,
default: '',
},
/**
* 背景类型,可选值为:
* - 'color': 使用纯色背景
* - 'transparent': 使用透明背景
* - 'image': 使用图片背景
*/
backgroundType: {
type: String as PropType<'color' | 'transparent' | 'image'>,
default: 'color',
validator: (value: string) => ['color', 'transparent', 'image'].includes(value),
},
/**
* 纯色背景颜色,仅当 backgroundType 为 'color' 时生效
*/
backgroundColor: {
type: String as PropType<string>,
default: 'black',
},
/**
* 背景图片路径,仅当 backgroundType 为 'image' 时生效
*/
backgroundImage: {
type: String as PropType<string>,
default: '',
},
/**
* 粒子色值
*/
particleColor: {
type: String as PropType<string>,
default: 'rgba(255, 255, 255, 0.5)',
},
});
interface Particle {
x: number;
y: number;
z: number;
}
const canvasRef = ref<HTMLCanvasElement | null>(null);
let ctx: CanvasRenderingContext2D | null = null;
let particles: Particle[] = [];
let animationFrameId: number | null = null;
const width = ref(0);
const height = ref(0);
// 动态计算背景图片样式
const computedBgImage = computed(() =>
props.backgroundType === 'image' && props.backgroundImage
? `url(${props.backgroundImage})`
: 'none',
);
// 初始化粒子
const initParticles = () => {
particles = Array.from({ length: props.particleCount }, () => ({
x: (Math.random() - 0.5) * width.value * 2,
y: (Math.random() - 0.5) * height.value * 2,
z: Math.random() * (props.maxDepth - props.minDepth) + props.minDepth,
}));
};
// 动画绘制逻辑
const draw = () => {
if (!ctx) return;
ctx.clearRect(0, 0, width.value, height.value);
for (const particle of particles) {
particle.z -= props.speed;
if (particle.z <= 0) {
particle.z = Math.random() * (props.maxDepth - props.minDepth) + props.minDepth;
}
const sx = width.value / 2 + (particle.x / particle.z) * props.fov;
const sy = height.value / 2 + (particle.y / particle.z) * props.fov;
const size = (1 - particle.z / props.maxDepth) * 5;
ctx.beginPath();
ctx.arc(sx, sy, size, 0, Math.PI * 2);
ctx.fillStyle = props.particleColor;
ctx.fill();
}
animationFrameId = requestAnimationFrame(draw);
};
// 初始化画布
const resizeCanvas = () => {
if (!canvasRef.value) return;
canvasRef.value.width = canvasRef.value.offsetWidth;
canvasRef.value.height = canvasRef.value.offsetHeight;
width.value = canvasRef.value.width;
height.value = canvasRef.value.height;
initParticles();
};
onMounted(() => {
const canvas = canvasRef.value;
if (!canvas) return;
ctx = canvas.getContext('2d');
resizeCanvas();
draw();
window.addEventListener('resize', resizeCanvas);
});
onBeforeUnmount(() => {
if (animationFrameId) cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resizeCanvas);
});
</script>
<style scoped>
.starfield-container {
width: 100%;
height: 100%;
overflow: hidden;
background-size: cover;
background-position: center;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
</style>
<template>
<div ref="container" class="particle-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
import * as THREE from 'three';
// 定义Props
const props = defineProps({
particleCount: { type: Number, default: 10000 }, // 每层粒子数量
layerCount: { type: Number, default: 4 }, // 层数
baseRadius: { type: Number, default: 200 }, // 基础圆环半径
waveAmplitude: { type: Number, default: 18 }, // 波动振幅
frequency: { type: Number, default: 6 }, // 波动频率(花瓣数)
particleSize: { type: Number, default: 1 }, // 粒子大小
rotationSpeed: { type: Number, default: 0.0005 }, // 整体旋转速度
waveSpeed: { type: Number, default: 0.08 }, // 波动速度
backgroundColor: { type: String, default: 'black' }, // 背景颜色
colors: {
type: Array as () => string[],
default: () => [
'hsl(180, 100%, 60%)',
'hsl(190, 100%, 60%)',
'hsl(200, 100%, 60%)',
'hsl(210, 100%, 60%)',
],
}, // 颜色数组 ,推荐使用hls数据,也可以使用rgb数据
});
// 元素容器和Three.js变量
const container = ref<HTMLDivElement | null>(null);
let scene: THREE.Scene, camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer;
let waveParticles: THREE.Points[] = [];
let animationId: number;
let time = 0; // 动画时间
// 初始化场景、相机和渲染器
function initScene() {
if (!container.value) return;
// 清理旧场景
waveParticles = [];
if (scene) {
scene.clear();
renderer.dispose();
}
scene = new THREE.Scene();
scene.background = new THREE.Color(props.backgroundColor);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.z = 500;
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
container.value.appendChild(renderer.domElement);
createWaveLayers();
animate();
}
// 创建波动层
function createWaveLayers() {
for (let i = 0; i < props.layerCount; i++) {
const geometry = new THREE.BufferGeometry();
const positions = [];
const sizes = [];
for (let j = 0; j < props.particleCount; j++) {
const angle = (j / props.particleCount) * Math.PI * 2; // 均匀分布在圆周上
const offset = Math.sin(angle * props.frequency + i * 1.5) * props.waveAmplitude; // 花瓣波动
const radius = props.baseRadius + offset; // 叠加振幅
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
const z = (Math.random() - 0.5) * 30; // Z轴增加颗粒感
positions.push(x, y, z);
sizes.push(Math.random() * 2 + 1); // 粒子大小随机
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
const color = props.colors[i % props.colors.length];
const material = new THREE.PointsMaterial({
size: props.particleSize,
// color: new THREE.Color(`hsl(${180 + i * 10}, 100%, 60%)`), // 层次颜色变化
color: new THREE.Color(color),
transparent: true,
opacity: 0.8 - i * 0.2,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
const points = new THREE.Points(geometry, material);
waveParticles.push(points);
scene.add(points);
}
}
// 动画逻辑
function animate() {
animationId = requestAnimationFrame(animate);
time += props.waveSpeed;
waveParticles.forEach((points, index) => {
const positions = points.geometry.attributes.position.array;
for (let i = 0; i < positions.length; i += 3) {
const angle = Math.atan2(positions[i + 1], positions[i]);
const baseRadius =
props.baseRadius + Math.sin(angle * props.frequency + index * 1.5) * props.waveAmplitude;
// 在初始位置基础上平滑地波动
const offset = Math.sin(angle * props.frequency + time + index * 0.5) * props.waveAmplitude;
positions[i] = Math.cos(angle) * (baseRadius + offset); // X轴更新
positions[i + 1] = Math.sin(angle) * (baseRadius + offset); // Y轴更新
}
points.geometry.attributes.position.needsUpdate = true;
// 控制每一层的旋转效果
points.rotation.z += props.rotationSpeed * (index % 2 === 0 ? 1 : -1);
});
renderer.render(scene, camera);
}
// 处理窗口大小变化
function onResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
// 监听Props变化,重新渲染
watch(props, () => {
initScene();
});
// 生命周期管理
onMounted(() => {
initScene();
window.addEventListener('resize', onResize);
});
onBeforeUnmount(() => {
cancelAnimationFrame(animationId);
window.removeEventListener('resize', onResize);
renderer.dispose();
scene.clear();
});
</script>
<style scoped>
.particle-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
import { Config } from 'tailwindcss'
import { Config } from 'tailwindcss';
function generateSizes(scale = 4) {
const sizes = {};
for (let i = 1; i <= 1000; i++) {
sizes[i] = `${i * scale}px`; // 动态生成规则,例如 w-100 = 400px
}
return sizes;
}
const config: Config = {
content: [
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}', // 扫描的文件路径
],
theme: {
extend: {}, // 自定义扩展
extend: {
width: generateSizes(4), // 设置宽度,每单位 = scale * px
height: generateSizes(4), // 设置高度,每单位 = scale * px
}, // 自定义扩展
},
plugins: [],
}
};
export default config
export default config;
......@@ -39,6 +39,8 @@ export default defineConfig(async ({ mode }) => {
return {
plugins: [
vue(),
// customLoaderPlugin(),
Components({
resolvers: [AntDesignVueResolver()],
dts: 'src/components.d.ts',
......@@ -48,9 +50,9 @@ export default defineConfig(async ({ mode }) => {
resolvers: [AntDesignVueResolver()],
dts: 'src/auto-imports.d.ts',
}),
customLoaderPlugin(),
],
css: {
postcss: './postcss.config.js',
preprocessorOptions: {
less: {
javascriptEnabled: true,
......
import { fileURLToPath } from 'node:url'
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
import viteConfig from './vite.config'
import { fileURLToPath } from 'node:url';
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config';
import viteConfig from './vite.config';
export default mergeConfig(
// @ts-ignore
viteConfig,
defineConfig({
test: {
......@@ -11,4 +12,4 @@ export default mergeConfig(
root: fileURLToPath(new URL('./', import.meta.url)),
},
}),
)
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment