Cesium地球风格切换、昼夜交替效果

昼夜交替:

风格切换:

矢量图层的风格切换:

影像图层风格切换:

动态开关昼夜效果:

封装源码:

switchLayers.js

javascript 复制代码
export const DEFAULT_TERRAIN_URL = 'http://data.mars3d.cn/terrain';

export const INITIAL_MAP_OPTIONS = {
    terrain: {
        url: DEFAULT_TERRAIN_URL
    },
    dayNightEffect: true,
    tianditu: false
};

export const TIANDITU_LAYER_ID_BY_TYPE = {
    1: 'tdt-vector',
    2: 'tdt-image',
    3: 'tdt-dizzy'
};

export const TIANDITU_TYPE_BY_LAYER_ID = {
    'tdt-vector': 1,
    'tdt-image': 2,
    'tdt-dizzy': 3
};

export const LAYER_GROUPS = [
    {
        id: 'general',
        title: '通用地图',
        options: [
            {
                id: 'default-layer',
                label: '默认底图',
                image: require('@/assets/layer/defaultLayer.png')
            }
        ]
    },
    {
        id: 'tianditu',
        title: '天地图',
        options: [
            {
                id: 'tdt-vector',
                label: '矢量图',
                image: require('@/assets/layer/vectorLayer.png')
            },
            {
                id: 'tdt-image',
                label: '影像图',
                image: require('@/assets/layer/imageLayer.png')
            },
            {
                id: 'tdt-dizzy',
                label: '晕眩图',
                image: require('@/assets/layer/dizzyLayer.png')
            }
        ]
    }
];

export const STYLE_OPTIONS = [
    {
        id: 'standard',
        label: '标准',
        desc: '恢复原始底图色彩',
        colors: ['#6fb2ff', '#3b7bd6', '#123a78']
    },
    {
        id: 'dark',
        label: '风格一',
        desc: '深海蓝调,压低亮度并保留地貌层次',
        colors: ['#020617', '#0f3a5c', '#2dd4bf']
    },
    {
        id: 'glow',
        label: '风格二',
        desc: '高饱和、高对比的蓝绿发光感',
        colors: ['#05161d', '#00d8ff', '#92ffdf']
    },
    {
        id: 'lava',
        label: '风格三',
        desc: '偏暖高对比,适合冲击视觉',
        colors: ['#1b0500', '#ff5a1f', '#ffd166']
    },
    {
        id: 'ice',
        label: '风格四',
        desc: '青绿极光色,提升冷色层次和科技感',
        colors: ['#021b2d', '#0f766e', '#a7f3d0']
    },
    {
        id: 'black',
        label: '纯黑',
        desc: '高对比灰阶暗黑,保留海陆边界',
        colors: ['#000000', '#2f3437', '#6b7280']
    },
    {
        id: 'cyber',
        label: '风格五',
        desc: '蓝紫强饱和科技风',
        colors: ['#08031c', '#6d35ff', '#00e5ff']
    },
    {
        id: 'grayscale',
        label: '风格六',
        desc: '去色并提高线条对比',
        colors: ['#111827', '#9ca3af', '#f8fafc']
    }
];

const STORAGE_KEY = 'web-drone-platform:test-switch-layers-settings';

export function getInitialMapState() {
    const defaultState = {
        activeLayerId: INITIAL_MAP_OPTIONS.tianditu
            ? TIANDITU_LAYER_ID_BY_TYPE[INITIAL_MAP_OPTIONS.tianditu.type]
            : 'default-layer',
        activeStyleId: 'standard',
        tiandituShowLabel: INITIAL_MAP_OPTIONS.tianditu
            ? INITIAL_MAP_OPTIONS.tianditu.showLabel
            : true,
        terrainEnabled: Boolean(INITIAL_MAP_OPTIONS.terrain),
        terrainUrl: INITIAL_MAP_OPTIONS.terrain ? INITIAL_MAP_OPTIONS.terrain.url : DEFAULT_TERRAIN_URL,
        dayNightEnabled: INITIAL_MAP_OPTIONS.dayNightEffect
    };

    try {
        const raw = window.sessionStorage.getItem(STORAGE_KEY);
        return raw ? { ...defaultState, ...JSON.parse(raw) } : defaultState;
    } catch (error) {
        return defaultState;
    }
}

export function saveMapState(state) {
    try {
        window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
    } catch (error) {
        // 测试页无需强依赖会话缓存。
    }
}

switchLayersScene.js

javascript 复制代码
import cesiumUtils from '@/utils/cesium/cesiumUtils.js';
import { DEFAULT_TERRAIN_URL, TIANDITU_TYPE_BY_LAYER_ID } from './switchLayers.js';

function buildTerrainOption(enabled, terrainUrl) {
    return enabled ? { url: terrainUrl || DEFAULT_TERRAIN_URL } : false;
}

export function initSwitchLayersScene(containerId, state) {
    const activeTiandituType = TIANDITU_TYPE_BY_LAYER_ID[state.activeLayerId];
    cesiumUtils.initViewer(containerId, {
        terrain: buildTerrainOption(state.terrainEnabled, state.terrainUrl),
        dayNightEffect: state.dayNightEnabled,
        ...(activeTiandituType
            ? {
                tianditu: {
                    type: activeTiandituType,
                    showLabel: state.tiandituShowLabel
                }
            }
            : {})
    });
    const layerState = cesiumUtils.setImageryLayerStyle(state.activeStyleId);
    const mapSettingsState = getSwitchLayersMapSettingsState(state.terrainUrl);
    return { layerState, mapSettingsState };
}

export function getSwitchLayersMapSettingsState(fallbackTerrainUrl) {
    const state = cesiumUtils.getMapSettingsState();
    return {
        terrainEnabled: state.terrainEnabled,
        terrainUrl: state.terrain && state.terrain.url
            ? state.terrain.url
            : fallbackTerrainUrl || DEFAULT_TERRAIN_URL,
        dayNightEnabled: state.dayNightEnabled
    };
}

export function setSwitchLayer(layerId, showLabel) {
    return cesiumUtils.setMapLayer(layerId, { showLabel });
}

export function setSwitchTiandituLabel(activeLayerId, showLabel) {
    if (activeLayerId.indexOf('tdt-') !== 0) return null;
    return cesiumUtils.setTiandituLayer(4, { showLabel });
}

export function setSwitchStyle(styleId) {
    return cesiumUtils.setImageryLayerStyle(styleId);
}

export function setSwitchTerrain(enabled, terrainUrl) {
    cesiumUtils.setTerrain(buildTerrainOption(enabled, terrainUrl));
}

export function setSwitchDayNight(enabled) {
    cesiumUtils.setGlobeDayNightEffect(enabled ? {} : false);
}

switchLayers.vue

javascript 复制代码
<template>
    <div class="switch-layers-page">
        <div id="init-viewer-wrapper"></div>
        <div ref="mapTools" class="map-tools">
            <button
                ref="layerButton"
                type="button"
                class="tool-btn"
                :class="{ 'tool-btn--active': activeToolPanel === 'layer' }"
                title="切换图层"
                @click.stop="toggleToolPanel('layer')"
            >
                <i class="iconfont icon-diqiu2 tool-btn__icon"></i>
            </button>
            <button
                ref="styleButton"
                type="button"
                class="tool-btn"
                :class="{ 'tool-btn--active': activeToolPanel === 'style' }"
                title="地球风格"
                @click.stop="toggleToolPanel('style')"
            >
                <i class="iconfont icon-diqiu1 tool-btn__icon"></i>
            </button>
            <button
                ref="settingsButton"
                type="button"
                class="tool-btn"
                :class="{ 'tool-btn--active': activeToolPanel === 'settings' }"
                title="地图设置"
                @click.stop="toggleToolPanel('settings')"
            >
                <i class="iconfont icon-xian1 tool-btn__icon"></i>
            </button>
        </div>

        <transition name="tool-popup-fade">
            <div
                v-if="activeToolPanel"
                ref="toolPopup"
                class="tool-popup"
                :style="toolPopupStyle"
                @click.stop
            >
                <section v-if="activeToolPanel === 'layer'" class="control-panel layer-control">
                    <div class="control-panel__topbar">
                        <div class="control-panel__eyebrow">Layers</div>
                        <div class="control-panel__title">图层选择</div>
                    </div>
                    <div class="layer-control__body">
                        <section
                            v-for="group in layerGroups"
                            :key="group.id"
                            class="layer-control__group"
                        >
                            <div class="layer-control__group-header">
                                <h3 class="layer-control__group-title">{{ group.title }}</h3>
                                <button
                                    v-if="group.id === 'tianditu'"
                                    type="button"
                                    class="layer-control__label-switch"
                                    :class="{ 'layer-control__label-switch--active': tiandituShowLabel }"
                                    @click="handleTiandituLabelChange(!tiandituShowLabel)"
                                >
                                    <span class="layer-control__switch-dot"></span>
                                    <span>注记</span>
                                </button>
                            </div>
                            <div class="layer-control__grid">
                                <button
                                    v-for="option in group.options"
                                    :key="option.id"
                                    type="button"
                                    class="layer-control__card"
                                    :class="{ 'layer-control__card--active': activeLayerId === option.id }"
                                    @click="handleLayerSelect(option.id)"
                                >
                                    <img class="layer-control__thumb" :src="option.image" :alt="option.label">
                                    <span class="layer-control__name">{{ option.label }}</span>
                                </button>
                            </div>
                        </section>
                    </div>
                </section>

                <section v-else-if="activeToolPanel === 'style'" class="control-panel style-control">
                    <div class="control-panel__topbar">
                        <div class="control-panel__eyebrow">Effects</div>
                        <div class="control-panel__title">地球风格</div>
                    </div>
                    <div class="style-control__grid">
                        <button
                            v-for="option in styleOptions"
                            :key="option.id"
                            type="button"
                            class="style-control__card"
                            :class="{ 'style-control__card--active': activeStyleId === option.id }"
                            @click="handleStyleSelect(option.id)"
                        >
                            <span
                                class="style-control__swatch"
                                :style="{ background: 'linear-gradient(135deg, ' + option.colors.join(', ') + ')' }"
                            ></span>
                            <span class="style-control__content">
                                <span class="style-control__name">{{ option.label }}</span>
                                <span class="style-control__desc">{{ option.desc }}</span>
                            </span>
                        </button>
                    </div>
                </section>

                <section v-else class="control-panel settings-control">
                    <div class="control-panel__topbar">
                        <div class="control-panel__eyebrow">Settings</div>
                        <div class="control-panel__title">地图设置</div>
                    </div>
                    <div class="settings-control__body">
                        <button
                            type="button"
                            class="settings-control__item"
                            :class="{ 'settings-control__item--active': terrainEnabled }"
                            @click="handleTerrainChange(!terrainEnabled)"
                        >
                            <span class="settings-control__content">
                                <span class="settings-control__name">三维地形</span>
                                <span class="settings-control__desc">启用 Mars3D 在线地形,关闭后切回椭球体地形</span>
                            </span>
                            <span class="settings-control__switch">
                                <span class="settings-control__switch-thumb"></span>
                            </span>
                        </button>
                        <button
                            type="button"
                            class="settings-control__item"
                            :class="{ 'settings-control__item--active': dayNightEnabled }"
                            @click="handleDayNightChange(!dayNightEnabled)"
                        >
                            <span class="settings-control__content">
                                <span class="settings-control__name">昼夜交替</span>
                                <span class="settings-control__desc">根据太阳光照显示昼夜明暗效果</span>
                            </span>
                            <span class="settings-control__switch">
                                <span class="settings-control__switch-thumb"></span>
                            </span>
                        </button>
                    </div>
                </section>
            </div>
        </transition>
    </div>
</template>

<script>
import cesiumUtils from '@/utils/cesium/cesiumUtils.js'
import {
    DEFAULT_TERRAIN_URL,
    LAYER_GROUPS,
    STYLE_OPTIONS,
    getInitialMapState,
    saveMapState
} from './switchLayers.js'
import {
    getSwitchLayersMapSettingsState,
    initSwitchLayersScene,
    setSwitchDayNight,
    setSwitchLayer,
    setSwitchStyle,
    setSwitchTerrain,
    setSwitchTiandituLabel
} from './switchLayersScene.js'

export default {
    name: 'depthDetection',
    components: {},
    data() {
        const mapState = getInitialMapState();
        return {
            layerGroups: LAYER_GROUPS,
            styleOptions: STYLE_OPTIONS,
            activeLayerId: mapState.activeLayerId,
            activeStyleId: mapState.activeStyleId,
            tiandituShowLabel: mapState.tiandituShowLabel,
            terrainEnabled: mapState.terrainEnabled,
            terrainUrl: mapState.terrainUrl,
            dayNightEnabled: mapState.dayNightEnabled,
            activeToolPanel: '',
            toolPopupPosition: {
                left: 0,
                top: 0
            }
        }
    },
    computed: {
        toolPopupStyle() {
            return {
                left: this.toolPopupPosition.left + 'px',
                top: this.toolPopupPosition.top + 'px'
            }
        }
    },
    mounted() {
        this.initViewer();
        window.addEventListener('resize', this.updateToolPopupPosition);
        document.addEventListener('pointerdown', this.handleOutsideToolPanelClick);
    },
    beforeDestroy() {
        window.removeEventListener('resize', this.updateToolPopupPosition);
        document.removeEventListener('pointerdown', this.handleOutsideToolPanelClick);
        cesiumUtils.destroy();
    },
    methods: {
        async initViewer() {
            const { layerState, mapSettingsState } = initSwitchLayersScene('init-viewer-wrapper', {
                activeLayerId: this.activeLayerId,
                activeStyleId: this.activeStyleId,
                tiandituShowLabel: this.tiandituShowLabel,
                terrainEnabled: this.terrainEnabled,
                terrainUrl: this.terrainUrl,
                dayNightEnabled: this.dayNightEnabled
            });
            this.syncLayerState(layerState);
            this.syncMapSettingsState(mapSettingsState);
        },
        syncLayerState(state) {
            this.activeLayerId = state.activeLayerId;
            this.activeStyleId = state.activeStyleId;
            this.tiandituShowLabel = state.showLabel;
            this.persistMapState();
        },
        syncMapSettingsState(nextState = null) {
            const state = nextState || getSwitchLayersMapSettingsState(this.terrainUrl);
            this.terrainEnabled = state.terrainEnabled;
            this.terrainUrl = state.terrainUrl;
            this.dayNightEnabled = state.dayNightEnabled;
            this.persistMapState();
        },
        persistMapState() {
            saveMapState({
                activeLayerId: this.activeLayerId,
                activeStyleId: this.activeStyleId,
                tiandituShowLabel: this.tiandituShowLabel,
                terrainEnabled: this.terrainEnabled,
                terrainUrl: this.terrainUrl || DEFAULT_TERRAIN_URL,
                dayNightEnabled: this.dayNightEnabled
            });
        },
        handleLayerSelect(layerId) {
            if (layerId === this.activeLayerId) return;
            this.syncLayerState(setSwitchLayer(layerId, this.tiandituShowLabel));
        },
        handleTiandituLabelChange(showLabel) {
            this.tiandituShowLabel = showLabel;
            const layerState = setSwitchTiandituLabel(this.activeLayerId, showLabel);
            if (layerState) {
                this.syncLayerState(layerState);
                return;
            }
            this.persistMapState();
        },
        handleStyleSelect(styleId) {
            if (styleId === this.activeStyleId) return;
            this.syncLayerState(setSwitchStyle(styleId));
        },
        handleTerrainChange(enabled) {
            this.terrainEnabled = enabled;
            setSwitchTerrain(enabled, this.terrainUrl);
            this.persistMapState();
        },
        handleDayNightChange(enabled) {
            this.dayNightEnabled = enabled;
            setSwitchDayNight(enabled);
            this.persistMapState();
        },
        getToolButton(panel) {
            if (panel === 'layer') return this.$refs.layerButton;
            if (panel === 'style') return this.$refs.styleButton;
            return this.$refs.settingsButton;
        },
        toggleToolPanel(panel) {
            this.activeToolPanel = this.activeToolPanel === panel ? '' : panel;
            this.$nextTick(this.updateToolPopupPosition);
        },
        updateToolPopupPosition() {
            if (!this.activeToolPanel) return;
            const button = this.getToolButton(this.activeToolPanel);
            const popup = this.$refs.toolPopup;
            if (!button || !popup) return;

            const gap = 12;
            const padding = 12;
            const buttonRect = button.getBoundingClientRect();
            const popupRect = popup.getBoundingClientRect();
            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;
            const topSpace = buttonRect.top;
            const bottomSpace = viewportHeight - buttonRect.bottom;
            const preferredTop = topSpace >= bottomSpace
                ? buttonRect.top - popupRect.height - gap
                : buttonRect.bottom + gap;
            const preferredLeft = buttonRect.left + (buttonRect.width - popupRect.width) / 2;

            this.toolPopupPosition = {
                left: Math.min(Math.max(preferredLeft, padding), viewportWidth - popupRect.width - padding),
                top: Math.min(Math.max(preferredTop, padding), viewportHeight - popupRect.height - padding)
            };
        },
        handleOutsideToolPanelClick(event) {
            if (!this.activeToolPanel) return;
            const target = event.target;
            if (!target) return;
            if (
                (this.$refs.toolPopup && this.$refs.toolPopup.contains(target)) ||
                (this.$refs.mapTools && this.$refs.mapTools.contains(target))
            ) {
                return;
            }
            this.activeToolPanel = '';
        }
    }
}
</script>

<style scoped>
.switch-layers-page {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: #020814;
}

#init-viewer-wrapper {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

.map-tools {
    position: absolute;
    left: 50%;
    bottom: 28px;
    z-index: 4;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: translateX(-50%);
}

.tool-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    margin: 0 2px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.88);
    color: #ffffff;
    cursor: pointer;
    font-size: 14px;
    font-weight: 700;
    box-shadow: 0 0 0 1px rgba(126, 233, 255, 0.08) inset, 0 10px 24px rgba(0, 0, 0, 0.28);
    transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

.tool-btn__icon {
    font-size: 18px;
    line-height: 1;
}

.tool-btn:hover,
.tool-btn--active {
    border-color: rgba(126, 233, 255, 0.78);
    background: rgba(0, 12, 20, 0.94);
    color: #7ee9ff;
    box-shadow: 0 0 0 1px rgba(126, 233, 255, 0.22) inset, 0 0 16px rgba(44, 197, 255, 0.18);
}

.tool-popup {
    position: fixed;
    z-index: 5;
}

.tool-popup-fade-enter-active,
.tool-popup-fade-leave-active {
    transition: opacity 0.22s ease, transform 0.22s ease;
}

.tool-popup-fade-enter,
.tool-popup-fade-leave-to {
    opacity: 0;
    transform: translateY(6px);
}

.control-panel {
    width: min(360px, calc(100vw - 24px));
    overflow: hidden;
    border: 1px solid rgba(164, 226, 255, 0.28);
    border-radius: 12px;
    background: linear-gradient(180deg, rgba(16, 22, 28, 0.62), rgba(10, 16, 22, 0.48)), rgba(0, 0, 0, 0.38);
    color: #eef8ff;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.04) inset, 0 16px 40px rgba(0, 0, 0, 0.32);
    backdrop-filter: blur(4px);
}

.control-panel__topbar {
    padding: 10px 12px 8px;
    border-bottom: 1px solid rgba(164, 226, 255, 0.12);
}

.control-panel__eyebrow {
    color: rgba(126, 233, 255, 0.72);
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

.control-panel__title {
    margin-top: 2px;
    font-size: 16px;
    font-weight: 700;
}

.layer-control__body {
    max-height: 390px;
    overflow-y: auto;
    padding: 10px 12px 12px;
}

.layer-control__group + .layer-control__group {
    margin-top: 10px;
}

.layer-control__group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 6px;
}

.layer-control__group-title {
    margin: 0;
    color: rgba(238, 248, 255, 0.94);
    font-size: 14px;
    font-weight: 700;
}

.layer-control__grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 7px;
    padding: 8px;
    border: 1px solid rgba(164, 226, 255, 0.18);
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.03);
}

.layer-control__card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    min-height: 82px;
    padding: 5px 4px 6px;
    border: 1px solid transparent;
    border-radius: 10px;
    background: transparent;
    color: rgba(238, 248, 255, 0.86);
    cursor: pointer;
    transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

.layer-control__card:hover,
.layer-control__card--active,
.style-control__card:hover,
.style-control__card--active,
.settings-control__item:hover,
.settings-control__item--active {
    border-color: rgba(126, 233, 255, 0.82);
    background: rgba(56, 151, 205, 0.18);
    color: #ffffff;
    box-shadow: 0 0 16px rgba(44, 197, 255, 0.18);
}

.layer-control__thumb {
    width: 58px;
    height: 48px;
    border-radius: 8px;
    object-fit: cover;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
}

.layer-control__name {
    width: 100%;
    min-height: 26px;
    font-size: 12px;
    font-weight: 700;
    line-height: 1.2;
    text-align: center;
}

.layer-control__label-switch {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    height: 24px;
    padding: 0 9px;
    border: 1px solid rgba(125, 232, 255, 0.26);
    border-radius: 999px;
    background: rgba(3, 29, 50, 0.72);
    color: rgba(230, 248, 255, 0.82);
    font-size: 12px;
    cursor: pointer;
}

.layer-control__switch-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(148, 163, 184, 0.86);
}

.layer-control__label-switch--active {
    border-color: rgba(126, 233, 255, 0.78);
    color: #ffffff;
}

.layer-control__label-switch--active .layer-control__switch-dot {
    background: #7ee9ff;
    box-shadow: 0 0 10px rgba(44, 197, 255, 0.8);
}

.style-control__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    padding: 10px 12px 12px;
}

.style-control__card,
.settings-control__item {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 60px;
    padding: 8px;
    border: 1px solid rgba(164, 226, 255, 0.14);
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.03);
    color: rgba(238, 248, 255, 0.86);
    cursor: pointer;
    text-align: left;
    transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

.style-control__swatch {
    flex: 0 0 34px;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22) inset, 0 4px 12px rgba(0, 0, 0, 0.28);
}

.style-control__content,
.settings-control__content {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: 3px;
}

.style-control__name,
.settings-control__name {
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
}

.style-control__desc,
.settings-control__desc {
    color: rgba(238, 248, 255, 0.62);
    font-size: 11px;
    line-height: 1.25;
}

.settings-control__body {
    display: grid;
    gap: 8px;
    padding: 10px 12px 12px;
}

.settings-control__item {
    justify-content: space-between;
    min-height: 66px;
}

.settings-control__switch {
    position: relative;
    flex: 0 0 46px;
    width: 46px;
    height: 24px;
    border: 1px solid rgba(125, 232, 255, 0.24);
    border-radius: 999px;
    background: rgba(3, 29, 50, 0.8);
}

.settings-control__switch-thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: rgba(148, 163, 184, 0.95);
    transition: background 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}

.settings-control__item--active .settings-control__switch {
    border-color: rgba(126, 233, 255, 0.78);
    background: rgba(44, 197, 255, 0.2);
}

.settings-control__item--active .settings-control__switch-thumb {
    background: #7ee9ff;
    box-shadow: 0 0 10px rgba(44, 197, 255, 0.8);
    transform: translateX(22px);
}
</style>
相关推荐
BJ-Giser7 小时前
CesiumJS升级全新VFX特效粒子系统
前端·可视化·cesium
白嫖叫上我9 小时前
Cesium抗锯齿处理
cesium
用户83134859306981 天前
Vue3 + Cesium 实现热气球第一人称自动飞行(支持手机端)
cesium
青山Coding2 天前
Cesium应用(六):三维地形中坡度分析的实现过程
前端·cesium
爱喝铁观音的谷力景辉5 天前
在Cesium中实现带箭头方向路线样式的技术详解
javascript·cesium
Nian.Baikal6 天前
Cesium 3D Tiles 加载与优化实战
前端·cesium
青山Coding10 天前
Cesium应用(五):通视分析,解锁三维场景的”无遮挡“视野
前端·cesium
BJ-Giser13 天前
Cesium 体积光阴影率分析和阴影体渲染效果
前端·可视化·cesium
小彭努力中16 天前
205.Vue3 + OpenLayers:加载动画,采用 CSS 的 @keyframes 方式
前端·css·vue.js·openlayers·cesium·webgis