<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Scene - shadow and lighting settings | Sample | ArcGIS Maps SDK for JavaScript</title>
<!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
<script type="module" src="https://js.arcgis.com/5.0/"></script>
#highlightShadowColorButton {
border: 1px solid rgb(177, 177, 177);
--calcite-block-section-content-space: 2px;
<arcgis-scene item-id="167f8a547ded4171abb2480a30022303">
<arcgis-popup slot="popup"></arcgis-popup>
<arcgis-home slot="top-left"></arcgis-home>
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
<arcgis-compass slot="top-left"> </arcgis-compass>
<div id="environmentDiv" slot="top-right">
<calcite-panel id="environmentSettings" heading="Environment Settings">
<div class="content-wrapper">
<calcite-label id="timeOfDaySelect-label"
<calcite-select id="timeOfDaySelect" label="Time of day selection">
<calcite-option value="Sun Mar 15 2019 08:00:00 GMT+0100 (CET)"
<calcite-option value="Sun Mar 15 2019 12:00:00 GMT+0100 (CET)"
<calcite-option value="Sun Mar 15 2019 16:00:00 GMT+0100 (CET)" selected
>Afternoon</calcite-option
<calcite-option value="Sun Mar 15 2019 18:00:00 GMT+0100 (CET)"
<calcite-label layout="inline-space-between"
<calcite-switch id="directShadowsSwitch" checked></calcite-switch>
<calcite-block-section text="Toggle shadows per layer" expanded>
<calcite-label layout="inline-space-between"
<calcite-switch id="castShadowsBuildings" checked></calcite-switch>
<calcite-label layout="inline-space-between"
<calcite-switch id="castShadowsZoningVolume"></calcite-switch>
<calcite-label layout="inline-space-between"
<calcite-switch id="castShadowsCrane"></calcite-switch>
text="Change shadow color of selected feature"
<calcite-label layout="inline-space-between"
id="highlightShadowColorButton"
label="Shadow Color"></calcite-swatch>
<div style="font-size: smaller; color: var(--calcite-color-text-2)">
Select a building or zoning volume to see the change
id="highlightShadowColorPicker"
scale="s"></calcite-color-picker>
const [Graphic, HighlightOptions, WebStyleSymbol] = await $arcgis.import([
"@arcgis/core/Graphic.js",
"@arcgis/core/views/support/HighlightOptions.js",
"@arcgis/core/symbols/WebStyleSymbol.js",
// Get a reference to the Scene, Popup and other html elements
const viewElement = document.querySelector("arcgis-scene");
const popupComponent = document.querySelector("arcgis-popup");
const timeOfDaySelect = document.querySelector("#timeOfDaySelect");
const directShadowsSwitch = document.querySelector("#directShadowsSwitch");
const castShadowsBuildings = document.querySelector("#castShadowsBuildings");
const castShadowsZoningVolume = document.querySelector("#castShadowsZoningVolume");
const castShadowsCrane = document.querySelector("#castShadowsCrane");
const colorButton = document.querySelector("#highlightShadowColorButton");
const colorPicker = document.querySelector("#highlightShadowColorPicker");
// Wait for the view to initialize
await viewElement.viewOnReady();
// Apply the initial environment settings to the view
viewElement.environment.lighting = {
date: new Date("Sun Jun 15 2025 16:00:00 GMT+0100 (CET)"),
directShadowsEnabled: true,
// Dock the Popup and set dockOptions
await popupComponent.componentOnReady();
popupComponent.dockEnabled = true;
popupComponent.dockOptions = {
// Disable the dock button so users cannot undock the popup
// Create a set of highlight options and add it to the view's highlights collection
const highlightOptions = new HighlightOptions({
viewElement.highlights = [highlightOptions];
// Set highlight shadow color to match selection highlight color
colorButton.color = highlightOptions.shadowColor.toString();
// Sync UI controls to the current view state
changeCastShadowBuildings();
changeCastShadowZoningVolume();
// Update the time of day in the view
function updateTimeOfDay() {
const selectedTimeOfDay = timeOfDaySelect.value;
viewElement.environment.lighting.date = new Date(selectedTimeOfDay);
// Toggle shadows in the view
function toggleDirectShadows() {
const shadowSwitchChecked = directShadowsSwitch.checked;
viewElement.environment.lighting.directShadowsEnabled = !!shadowSwitchChecked;
// Toggle building shadows
function changeCastShadowBuildings() {
const layer = viewElement.map.layers.getItemAt(0);
console.warn("Buildings layer not found at index 0");
!layer.renderer.symbol ||
!layer.renderer.symbol.symbolLayers?.length
"Buildings layer renderer or symbolLayers not available to change castShadows",
const newRenderer = layer.renderer.clone();
const firstSymbolLayer = newRenderer.symbol.symbolLayers.getItemAt(0);
console.warn("No symbol layer available on buildings renderer");
newRenderer.symbol.symbolLayers.getItemAt(0).castShadows = !!castShadowsBuildings.checked;
layer.renderer = newRenderer;
// Toggle zoning volume shadows
function changeCastShadowZoningVolume() {
updateCastShadowZoningVolume(!!castShadowsZoningVolume.checked);
// Update zoning volume shadows
function updateCastShadowZoningVolume(castShadow) {
const layer = viewElement.map.layers.getItemAt(1);
console.warn("Zoning volume layer not found at index 1");
!layer.renderer.symbol ||
!layer.renderer.symbol.symbolLayers?.length
"Zoning volume renderer or symbolLayers not available to change castShadows",
const newRenderer = layer.renderer.clone();
const firstSymbolLayer = newRenderer.symbol.symbolLayers.getItemAt(0);
console.warn("No symbol layer available on zoning volume renderer");
newRenderer.symbol.symbolLayers.getItemAt(0).castShadows = castShadow;
layer.renderer = newRenderer;
function changeCastShadowCrane() {
updateCastShadowCrane(!!castShadowsCrane.checked);
function updateCastShadowCrane(castShadow) {
const graphic = viewElement.graphics.getItemAt(0);
console.warn("Crane graphic not in view.");
if (!graphic.symbol || !graphic.symbol.symbolLayers?.length) {
console.warn("Crane symbol or symbolLayers not available to change castShadows");
const newSymbol = graphic.symbol.clone();
const firstSymbolLayer = newSymbol.symbolLayers.getItemAt(0);
console.warn("No symbol layer available on crane symbol");
newSymbol.symbolLayers.getItemAt(0).castShadows = castShadow;
graphic.symbol = newSymbol;
// Change shadow color of the highlight
function changeShadowColor() {
highlightOptions.shadowColor = colorPicker.value.toString();
colorButton.color = colorPicker.value.toString();
// Helper function to toggle the color picker visibility
function toggleColorPicker() {
if (colorPicker.style.display === "none") {
colorPicker.setAttribute("value", highlightOptions.shadowColor.toHex());
colorPicker.style.display = "block";
colorPicker.style.display = "none";
// Helper function adding a point symbolized as 3D crane
function createCraneGraphic() {
const craneSymbol = new WebStyleSymbol({
styleName: "EsriRealisticTransportationStyle",
craneSymbol.fetchSymbol().then((resolvedSymbol) => {
const crane = new Graphic({
spatialReference: viewElement.spatialReference,
viewElement.graphics.add(crane);
updateCastShadowCrane(castShadowsCrane.checked);
// Register events to controls
timeOfDaySelect.addEventListener("calciteSelectChange", updateTimeOfDay);
directShadowsSwitch.addEventListener("calciteSwitchChange", toggleDirectShadows);
castShadowsBuildings.addEventListener("calciteSwitchChange", changeCastShadowBuildings);
castShadowsZoningVolume.addEventListener("calciteSwitchChange", changeCastShadowZoningVolume);
castShadowsCrane.addEventListener("calciteSwitchChange", changeCastShadowCrane);
colorPicker.addEventListener("calciteColorPickerChange", changeShadowColor);
colorButton.addEventListener("click", toggleColorPicker);