OpenLayers 动画

关注我,带你一起学GIS ^

注:当前使用的是 ol 9.2.4 版本,天地图使用的key请到天地图官网申请,并替换为自己的key

动画在WebGIS开发中是很常见的操作,一般具有平移动画、旋转动画、弹性动画等。在OpenLayers开发中,地图动画主要由View类的animate方法实现。animate可以实现多段动画效果,其中参数包括zoomcenterresolution等。

参考动画地址:https://easings.net/#

1. 普通平移动画

普通平移动画方便简单,获取当前视图缩放级别,设置平移中心点即可。

view.animate({
  zoom: view.getZoom(),
  center
});

2. 弹性平移动画

弹性平移在普通平移的基础上加上动画函数。

view.animate(
    {
        zoom: 10,
        center,
        duration: 2000,
        easing: elastic
    }
);

3. 弹跳平移动画

弹跳平移可以设置多段动画,可以使用OpenLayers提供的动画函数upAndDown,也可以自定义动画函数。

view.animate(
    //第一段动画
    {
        zoom: zoom - 1,
        center,
        duration: 1000,
        // easing: ol.easing.upAndDown
        easing: easeInBounce
    },
    //第二段动画
    {
        zoom: zoom,
        center,
        duration: 1000,
        // easing: ol.easing.upAndDown
        easing: easeInBounce
    }
);

4. 旋转平移动画

旋转动画主要设置rotation参数,其值为弧度大小,-Math.PI * 2<=rotation<=Math.PI * 2

view.animate(
  {
    zoom: 10,
    center,
    rotation: Math.PI
  },
  {
    zoom: 10,
    center,
    rotation: Math.PI * 2
  }
);

5. 飞行平移动画

在飞行动画中,可以设置多段动画效果,改变飞行过程中缩放级别参数,在动画结束时恢复动画开始之前的缩放级别。

view.animate(
  {
    zoom: zoom,
    center,
    duration: 2000
  },
  {
    zoom: zoom - 1,
    center,
    duration: 1000
  },
  {
    zoom: zoom,
    center,
    duration: 1000
  }
)

6. 完整代码

其中libs文件夹下的包需要更换为自己下载的本地包或者引用在线资源。

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>地图动画</title>
    <meta charset="utf-8" />
    <script src="../../libs/js/jquery-2.1.1.min.js"></script>
    <script src="../../js/ol9.2.4.js"></script>
    <link rel="stylesheet" href="../../css/ol9.2.4.css">
    <style>
        * {
            padding: 0;
            margin: 0;
            font-size: 14px;
            font-family: '微软雅黑';
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        #map {
            position: absolute;
            top: 50px;
            bottom: 0;
            width: 100%;
        }

        #top-content {
            position: absolute;
            width: 100%;
            height: 50px;
            line-height: 50px;
            background: linear-gradient(135deg, #ff00cc, #ffcc00, #00ffcc, #ff0066);
            color: #fff;
            text-align: center;
        }

        .animate {
            border-radius: 5px;
            border: 1px solid #50505040;
            padding: 5px 20px;
            color: #fff;
            margin: 0 10px;
            background: #377d466e;
            transition: background-color 10s ease-in-out 10s;
            text-align: center;
            font-size: 16px;
            font-weight: bold;
        }

        .animate:hover {
            cursor: pointer;
            filter: brightness(120%);
            background: linear-gradient(135deg, #c850c0, #4158d0);
        }

        .active {
            background: linear-gradient(135deg, #c850c0, #4158d0);
        }

        .ol-popup {
            position: relative;
            font-size: 16px;
            color: #4c4c4c;
            background-color: #FFF;
            border-radius: 5px;
        }

        .ol-popup::before {
            display: block;
            content: "";
            width: 0;
            height: 0;
            border-left: 15px solid transparent;
            border-right: 15px solid transparent;
            border-top: 10px solid #FFF;
            position: absolute;
            bottom: -8px;
            left: 50%;
            transform: translateX(-50%);
        }

        .ol-popup-main {
            padding: 20px;
        }

        .ol-popup-close {
            position: absolute;
            display: inline-block;
            top: -6px;
            right: 5px;
            color: #878282b5;
            font-size: 20px;
        }

        .ol-popup-text {
            font-size: 14px;
            font-weight: bold;
            color: #434343;
        }

        .ol-popup-close:hover {
            cursor: pointer;
            color: #0e0e0eb5;
            filter: brightness(120%);
        }
    </style>
</head>

<body>
    <div id="map" title="地图显示"></div>
    <div id="top-content">
        <button class="animate simple-animate">普通平移</button>
        <button class="animate flex-animate">弹性平移</button>
        <button class="animate jump-animate">弹跳平移</button>
        <button class="animate rotate-animate">旋转平移</button>
        <button class="animate fly-animate">飞行平移</button>
        <button class="animate reset">复位</button>
    </div>
</body>

</html>

<script>
    //地图投影坐标系
    const projection = ol.proj.get('EPSG:3857');
    //==============================================================================//
    //============================天地图服务参数简单介绍==============================//
    //================================vec:矢量图层==================================//
    //================================img:影像图层==================================//
    //================================cva:注记图层==================================//
    //======================其中:_c表示经纬度投影,_w表示球面墨卡托投影================//
    //==============================================================================//
    const TDTImgLayer = new ol.layer.Tile({
        title: "天地图影像图层",
        source: new ol.source.XYZ({
            url: "http://t0.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=2a890fe711a79cafebca446a5447cfb2",
            attibutions: "天地图注记描述",
            crossOrigin: "anoymous",
            wrapX: false
        })
    })
    const TDTImgCvaLayer = new ol.layer.Tile({
        title: "天地图影像注记图层",
        source: new ol.source.XYZ({
            url: "http://t0.tianditu.com/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=2a890fe711a79cafebca446a5447cfb2",
            attibutions: "天地图注记描述",
            crossOrigin: "anoymous",
            wrapX: false
        })
    })
    const map = new ol.Map({
        target: "map",
        loadTilesWhileInteracting: true,
        view: new ol.View({
            center: [104.0635986160487, 30.660919181071225],
            // center: [11444274, 12707441],
            zoom: 5,
            worldsWrap: true,
            minZoom: 1,
            maxZoom: 20,
            projection: "EPSG:4326"
        }),
        layers: [TDTImgLayer, TDTImgCvaLayer],
        // 地图默认控件
        controls: ol.control.defaults.defaults({
            zoom: true,
            attribution: true,
            rotate: true
        })
    })
    map.on('click', evt => {
        console.log(evt.coordinate)
    })

    /**
     * 添加图层
     */
    function addPoint(center, popupEle) {
        const point = new ol.source.Vector({
            features: [
                new ol.Feature({
                    geometry: new ol.geom.Point(center)
                })
            ]
        })
        const layer = new ol.layer.Vector({
            source: point,
            style: new ol.style.Style({
                image: new ol.style.Circle({
                    radius: 5,
                    fill: new ol.style.Fill({
                        color: "red"
                    })
                })
            })
        })
        layer.set("name""pointLayer")

        const marker = new ol.Overlay({
            id: "maker",
            position: center,
            element: popupEle,
            offset: [0, -20], // x、y轴偏移量,正值向右向下,负值相反
            positioning: 'bottom-center', // 定位方式,顶部居中
            // aotuPan: true,
            // autoPanMargin: 1.25,
        })
        map.addOverlay(marker)
        map.addLayer(layer)
    }

    /**
     * 移除图层
     */
    function clearPoint() {
        const layers = map.getLayers().getArray()
        layers.forEach(layer => {
            if (layer.get("name") === "pointLayer") {
                map.removeLayer(layer)
            }
        })

        const overlays = map.getOverlays().getArray()
        overlays.forEach(overlay => {
            map.removeOverlay(overlay)
        })
    }

    const view = map.getView()
    /**
     * 普通平移
     */
    const simpleAnimateEle = document.querySelector(".simple-animate")
    simpleAnimateEle.addEventListener("click", evt => {
        clearPoint()
        toogleAciveClass(evt.target)
        const center = [24.22230887826462, 11.917610630971534]
        view.animate({
            zoom: view.getZoom(),
            center
        });
        const popupEle = createPopupEle("这里是非洲!")
        addPoint(center, popupEle)
    })

    // 获取弹性伸缩值
    function elastic(t) {
        return Math.pow(2, -10 * t) * Math.sin(t - 0.075) * (2 * Math.PI) + 1

    }
    /**
     * 弹性平移
     */  
    const flexAnimateEle = document.querySelector(".flex-animate")
    flexAnimateEle.addEventListener("click", evt => {
        clearPoint()
        toogleAciveClass(evt.target)
        const center = [120.1585181123716, 30.252189829156805]
        view.animate(
            {
                zoom: 10,
                center,
                duration: 2000,
                easing: elastic
            }
        );
        const popupEle = createPopupEle("这里是杭州!")
        addPoint(center, popupEle)
    })

    function easeOutBounce(x) {
        const n1 = 7.5625;
        const d1 = 2.75;
        if (x < 1 / d1) {
            return n1 * x * x;
        } else if (x < 2 / d1) {
            return n1 * (x -= 1.5 / d1) * x + 0.75;
        } else if (x < 2.5 / d1) {
            return n1 * (x -= 2.25 / d1) * x + 0.9375;
        } else {
            return n1 * (x -= 2.625 / d1) * x + 0.984375;
        }
    }
    function easeInBounce(number) {
        return 1 - easeOutBounce(1 - number);
    }
    /**
     * 弹跳平移
     */  
    const jumpAnimateEle = document.querySelector(".jump-animate")
    jumpAnimateEle.addEventListener("click", evt => {
        clearPoint()
        toogleAciveClass(evt.target)
        const center = [103.83814721904622, 36.05218284753544]
        const zoom = view.getZoom()
        view.animate(
            {
                zoom: zoom - 1,
                center,
                duration: 1000,
                // easing: ol.easing.upAndDown
                easing: easeInBounce
            },
            {
                zoom: zoom,
                center,
                duration: 1000,
                // easing: ol.easing.upAndDown
                easing: easeInBounce
            }
        );
        const popupEle = createPopupEle("这里是兰州!")
        addPoint(center, popupEle)
    })

    /**
     * 旋转平移
     */  
    const rotateAnimateEle = document.querySelector(".rotate-animate")
    rotateAnimateEle.addEventListener("click", evt => {
        clearPoint()
        toogleAciveClass(evt.target)
        const center = [104.0635986160487, 30.660919181071225]
        view.animate(
            {
                zoom: 10,
                center,
                rotation: Math.PI
            },
            {
                zoom: 10,
                center,
                rotation: Math.PI * 2
            }
        );
        const popupEle = createPopupEle("这里是成都!")
        addPoint(center, popupEle)
    })
    /**
     * 飞行平移
     */    
    const flyAnimateEle = document.querySelector(".fly-animate")
    flyAnimateEle.addEventListener("click", evt => {
        clearPoint()
        toogleAciveClass(evt.target)
        const center = [113.2562927875718, 23.134318823592487]
        const zoom = view.getZoom()
        view.animate(
            {
                zoom: zoom,
                center,
                duration: 2000
            },
            {
                zoom: zoom - 1,
                center,
                duration: 1000
            },
            {
                zoom: zoom,
                center,
                duration: 1000
            }
        )
        const popupEle = createPopupEle("这里是广州!")
        addPoint(center, popupEle)
    })
    /**
     * 复位
     */  
    document.querySelector(".reset").addEventListener('click', evt => {
        clearPoint()
        toogleAciveClass(evt.target)
        const center = [112.21975149119689, 30.33501910629522]
        view.animate({ zoom: view.getZoom() }, { center: center }, { rotation: 0 });
        const popupEle = createPopupEle("这里是荆州!")
        addPoint(center, popupEle)
    })
    /**
     * 切换激活样式
     */
    function toogleAciveClass(target) {
        // 判断top-content子元素是否激活,并切换激活样式
        const swipeBtnLike = document.querySelector('#top-content')
        for (let element of swipeBtnLike.children) {
            if (target === element) {
                target.classList.add('active')
            } else {
                element.classList.remove('active')
            }
        }
    }

    /**
     * 创建Popup 内容
     * @mainContent:HTML 或者 文字
     */
    function createPopupEle(mainContent) {
        const htmlEle = document.createElement("div")
        const closeEle = document.createElement('span')
        const mainEle = document.createElement('div')

        mainEle.className = 'ol-popup-main'
        console.log(typeof mainContent)
        const contentType = typeof mainContent
        if (contentType === 'object') {
            mainEle.appendChild(mainContent)
        } else if (contentType === 'string') {
            mainEle.innerHTML = mainContent
        }

        closeEle.textContent = "×"
        closeEle.className = "ol-popup-close"
        htmlEle.className = "ol-popup"

        htmlEle.appendChild(mainEle)
        htmlEle.appendChild(closeEle)

        // 注册关闭popup事件
        closeEle.addEventListener('click', evt => {
            clearPoint()
        })

        return htmlEle
    }
</script>

OpenLayers示例数据下载,请在公众号后台回复:ol数据

全国信息化工程师-GIS 应用水平考试资料,请在公众号后台回复:GIS考试

GIS之路公众号已经接入了智能助手,欢迎大家前来提问。

欢迎访问我的博客网站-长谈GIShttp://shanhaitalk.com

都看到这了,不要忘记点赞、收藏+关注 

本号不定时更新有关 GIS开发  相关内容,欢迎关注 

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部