ArcGIS API 4.x實現SceneView的捲簾功能

本篇博客主要是在ArcGIS API 4.12的環境下, 實現捲簾功能

話不多說, 上代碼

<!DOCTYPE html>
<html>
  <head>
      <style>
          html,
          body {
            padding: 0;
            margin: 0;
            height: 100%;
          }
          /* 垂直分割線 */
          .vertical_line{
            position: absolute;
            top: 12%;
            left: 45%;
            bottom:  4.2%;
            z-index: 1;
            float:left;
            width: 10px;
            background-color: rgba(50,50,50,0.75);
            user-select: none;
          }
          /* 設置存放地圖div的樣式 */
          #esri_view_div {
            position: absolute;
            top: 12%;
            bottom: 4.5%;
            left: 2.4%;
            right: 2.4%;
          }
          /* 圓按鈕 */
          .circle {
            width: 30px;
            height: 30px;
            background-color: rgba(50,50,50,0.75);
            border-radius: 50%;
            position: absolute;
            top: 40%;
            left: 40%;
            bottom:  4.2%;
            z-index: 2;
            margin-left: -10px;
            user-select: none;
          }
          #esri_view_div_swipe {
            position: absolute;
            top: 12%;
            bottom: 4.5%;
            left: 2.4%;
            right: 2.4%;
            z-index: -1;
          }
          /* 左箭頭 */
          .triangle-left {
            width: 0;
            height: 0;
            border-top: 4px solid transparent;
            border-right: 7px solid white;
            border-bottom: 4px solid transparent;
            position: absolute;
            top: 40%;
            margin-left: -8px;
            margin-top: 12px;
            z-index: 3;
            user-select: none;
          }
          /* 右箭頭 */
          .triangle-right {
            width: 0;
            height: 0;
            border-top: 4px solid transparent;
            border-left: 7px solid white;
            border-bottom: 4px solid transparent;
            position: absolute;
            top: 40%;
            margin-left: 10px;
            margin-top: 12px;
            z-index: 3;
            user-select: none;
          }
        </style>

        <link
          rel="stylesheet"
          href="https://js.arcgis.com/4.12/esri/themes/light/main.css"
        />
        <script src="https://js.arcgis.com/4.12/"></script>
        <script>
          require([
            "esri/Map",
            "esri/views/SceneView",
            "esri/core/watchUtils",
            'dojo/dom-style',
          ], function(Map, SceneView, watchUtils, EsriDomStyle) {
            var esriContainerDiv = 'esri_view_div'
            var esriSwipeContainerDiv = 'esri_view_div_swipe'
            var map = new Map({
              basemap: "satellite"
            });
            var map2 = new Map({
              basemap: "osm"
            });

            var view1 = new SceneView({
              container: "esri_view_div",
              map: map
            });

            var view2 = new SceneView({
              container: esriSwipeContainerDiv,
              map: map2,
            });
            view1.ui.remove(['attribution', 'zoom', 'navigation-toggle', 'compass'])
            view2.ui.remove(['attribution', 'zoom', 'navigation-toggle', 'compass'])
            var isSlitLineDragging = false // 分割線移動狀態
            document.getElementById('swipe_split_box').onmousedown = function () {
              isSlitLineDragging = true
            }
            document.getElementById('swipe_split_box').onmouseup = function() {
              isSlitLineDragging = false
            }
            /**
             * 分割線移動事件
             * @param {Object} e 分割線移動事件對象
             */
            function pointMove(e) {
              e.stopPropagation()
              updateMapSwipeLocation(e.x)
            };
            /**
             * 更新捲簾地圖容器展開位置
             * @param {Number} location 當前的位置
             * @param {Boolean} isInit 是否是初始化
             */
            function updateMapSwipeLocation(location, isInit) {
              const swipeMap = document.getElementById(esriSwipeContainerDiv).getBoundingClientRect()
              const offsetX = location
              if (isSlitLineDragging || isInit) {
                EsriDomStyle.set(esriSwipeContainerDiv, 'z-index', '1')
                EsriDomStyle.set(esriSwipeContainerDiv, 'clip', 'rect(0px,' + offsetX + 'px, ' + swipeMap.height + 'px,0px)')
                EsriDomStyle.set('vertical_line', 'left', (offsetX - 5 + (swipeMap.width * 0.024)) + 'px ')
                EsriDomStyle.set('swipe_circle', 'left', (offsetX - 5 + (swipeMap.width * 0.024)) + 'px ')
                EsriDomStyle.set('swipe_triangle_left', 'left', (offsetX - 5 + (swipeMap.width * 0.024)) + 'px ')
                EsriDomStyle.set('swipe_triangle_right', 'left', (offsetX - 5 + (swipeMap.width * 0.024)) + 'px ')
              }
            };
             /**
             * 同步兩個視圖容器
             * @param {Object} view 視圖容器
             * @param {Object} others 其它的視圖容器
             * @return {Object} 監聽事件
             */
            function synchronizeView(view, others) {
              others = Array.isArray(others) ? others : [others]

              let viewpointWatchHandle
              let viewStationaryHandle
              let otherInteractHandlers
              let scheduleId

              const clear = function() {
                if (otherInteractHandlers) {
                  otherInteractHandlers.forEach(function(handle) {
                    handle.remove()
                  })
                }
                viewpointWatchHandle && viewpointWatchHandle.remove()
                viewStationaryHandle && viewStationaryHandle.remove()
                scheduleId && clearTimeout(scheduleId)
                otherInteractHandlers = viewpointWatchHandle = viewStationaryHandle = scheduleId = null
              }

              const interactWatcher = view.watch('interacting, animation', (newValue) => {
                if (!newValue) {
                  return
                }
                if (viewpointWatchHandle || scheduleId) {
                  return
                }

                // start updating the other views at the next frame
                scheduleId = setTimeout(function() {
                  scheduleId = null
                  viewpointWatchHandle = view.watch('viewpoint', (newValue) => {
                    others.forEach(function(otherView) {
                      otherView.viewpoint = newValue
                    })
                  })
                }, 0)
                const that = this
                // stop as soon as another view starts interacting, like if the user starts panning
                otherInteractHandlers = others.map(function(otherView) {
                  return watchUtils.watch(otherView, 'interacting,animation',
                      (value) => {
                        if (value) {
                          clear()
                        }
                      }
                  )
                })

                // or stop when the view is stationary again
                viewStationaryHandle = watchUtils.whenTrue(view, 'stationary', clear)
              })

              return {
                remove: function() {
                  this.remove = function() {}
                  clear()
                  interactWatcher.remove()
                },
              }
            };
            /**
             * 同步兩個視圖容器入口函數
             * @param {Object} views 多個視圖容器
             * @return {Object} 移除事件
             */
            function synchronizeViews(views) {
              let handles = views.map(function(view, idx, views) {
                const others = views.concat()
                others.splice(idx, 1)
                return synchronizeView(view, others)
              })
              return {
                remove: function() {
                  this.remove = function() {}
                  handles.forEach(function(h) {
                    h.remove()
                  })
                  handles = null
                },
              }
            };
            view1.on('pointer-move', (e) => {
              pointMove(e)
            })
            view2.on('pointer-move', (e) => {
              pointMove(e)
            })
            // 設置初始位置
            const swipeMap = document.getElementById(esriSwipeContainerDiv).getBoundingClientRect()
            updateMapSwipeLocation(swipeMap.width * 0.5, true)
            // 同步視圖
            synchronizeViews([view1, view2])
          })
        </script>
  </head>
  <body>
    <div id='esri_view_div'></div>
    <div id='esri_view_div_swipe'></div>
    <div id="swipe_split_box">
      <div id="vertical_line" class="vertical_line"></div>
      <div id="swipe_circle" class="circle"></div>
      <div id="swipe_triangle_left" class="triangle-left"></div>
      <div id="swipe_triangle_right" class="triangle-right"></div>
    </div>
  </body>
</html>

總結

捲簾實現主要分爲兩個部分
   1. 視圖容器Div的Clip
      1) 開始捲簾, 即使用最關鍵的功能: css的clip屬性, 將視圖容器的div進行切分以實現捲簾
      2) 初始化視圖容器esriSceneView和地圖分割線的位置, 使之出現在中間
      3) 在按下分割線的時候, 使之進入拖動狀態, 拖動狀態時計算當前鼠標位置, 以及根據計算到的位置進行設置視圖容器的clip: rect屬性
      關於clip: rect的幾個參數說明https://www.zhangxinxu.com/study/201103/css-rect-demo.html
      4) 結束拖動, 關閉拖動狀態
  2. 兩個視圖容器的聯動
      1) 通過兩個view互相監聽interacting, animation兩個屬性, 
          當着兩個屬性變化的時候獲得該視圖容器的viewpoint, 同步設置另一個視圖容器的viewpoint
      注: interacting : boolean, 指示是否與視圖交互(例如平移時)。
            animation: ViewAnimation, 表示由goTo()初始化的正在進行的視圖動畫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章