1 Cesium.knockout 简介
我们在使用CesiumJS进行web项目开发的过程中通常会有这样的需求:在页面上通过拖动滑块的方式来实时改变三维地球图层的透明度等参数;在input输入框中输入不同的值来实时改变Entity实体的大小、朝向、颜色等参数;…;诸如此类的需求我们如果使用原生JS去实现,难免有些复杂,并且效果也不是非常好。
了解前端框架VUE的童鞋都知道,它是MVVM模式的框架,双向绑定是它的一大特色,但其实2011年发布的KnockoutJS才是MVVM的鼻祖,KnockoutJS给了VUE很多启发。
以上需求使用VUE和KnockoutJS都可以解决上述需求,而CesiumJS已经在源码中引入了knockout.js并对其进行了改造,当前最新版Cesium 1.88源码中引入的是knockout.js的3.5.1版本。
以下将简单介绍一下knockout.js。
2 knockout.js 简介
Knockout是一个轻量级的UI类库,通过应用MVVM模式使JavaScript前端UI简单化。Knockout是一个以数据模型(data model)为基础的能够帮助你创建富文本,响应显示和编辑用户界面的JavaScript类库。任何时候如果你的UI需要自动更新(比如:更新依赖于用户的行为或者外部数据源的改变),KO能够很简单的帮你实现并且很容易维护。
Knockout有如下4大重要概念:
- 声明式绑定 (Declarative Bindings):使用简明易读的语法很容易地将模型(model)数据关联到DOM元素上。
- UI界面自动刷新 (Automatic UI Refresh):当您的模型状态(model state)改变时,您的UI界面将自动更新。
- 依赖跟踪 (Dependency Tracking):为转变和联合数据,在你的模型数据之间隐式建立关系。
- 模板 (Templating):为您的模型数据快速编写复杂的可嵌套的UI。
重要特性:
- 优雅的依赖追踪- 不管任何时候你的数据模型更新,都会自动更新相应的内容。
- 声明式绑定- 浅显易懂的方式将你的用户界面指定部分关联到你的数据模型上。
- 灵活全面的模板- 使用嵌套模板可以构建复杂的动态界面。
- 轻易可扩展- 几行代码就可以实现自定义行为作为新的声明式绑定。
额外的好处:
- 纯JavaScript类库 – 兼容任何服务器端和客户端技术
- 可添加到Web程序最上部 – 不需要大的架构改变
- 简洁的 – Gzip之前大约25kb
- 兼容任何主流浏览器 (IE 6+、Firefox 2+、Chrome、Safari、其它)
- Comprehensive suite of specifications (采用行为驱动开发) - 意味着在新的浏览器和平台上可以很容易通过验证。
3 Cesium.knockout 用法
Cesium.knockout在对knockout.js改造后用法上与knockout.js有点区别,但是使用knockout.js自身的用法也是可行的。以下代码要达到的效果如下图:
3.1 Cesium.knockout改造后的用法
完整代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.88/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.88/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html,
body,
#cesiumContainer {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.inputPanel {
position: absolute;
background-color: aliceblue;
top: 20px;
left: 20px;
padding: 10px;
border-radius: 10px;
}
.title {
font-size: 20px;
font-weight: bold;
width: 100%;
text-align: center;
}
.form-item {
display: flex;
align-items: center;
justify-content: center;
margin: 10px;
}
.form-item .label {
width: 80px;
}
.form-item .value {
width: 40px;
}
</style>
<title>Cesium.knockout示例</title>
</head>
<body>
<div id="cesiumContainer"></div>
<div id="inputPanel" class="inputPanel">
<div class="title">图层参数调整</div>
<div class="form-item">
<div class="label">透明度:</div>
<input type="range" min="0.0" max="1.0" step="0.01" data-bind="value: alpha, valueUpdate: 'input'">
<div class="value" data-bind="html: alpha"></div>
</div>
<div class="form-item">
<div class="label">亮度:</div>
<input type="range" min="0.0" max="10" step="0.01" data-bind="value: brightness, valueUpdate: 'input'">
<div class="value" data-bind="html: brightness"></div>
</div>
<div class="form-item">
<div class="label">对比度:</div>
<input type="range" min="0.0" max="10" step="0.01" data-bind="value: contrast, valueUpdate: 'input'">
<div class="value" data-bind="html: contrast"></div>
</div>
<div class="form-item">
<div class="label">色调:</div>
<input type="range" min="0.0" max="6.283" step="0.01" data-bind="value: hue, valueUpdate: 'input'">
<div class="value" data-bind="html: hue"></div>
</div>
<div class="form-item">
<div class="label">饱和度:</div>
<input type="range" min="0.0" max="10" step="0.01" data-bind="value: saturation, valueUpdate: 'input'">
<div class="value" data-bind="html: saturation"></div>
</div>
<div class="form-item">
<div class="label">伽马值:</div>
<input type="range" min="0.0" max="10" step="0.01" data-bind="value: gamma, valueUpdate: 'input'">
<div class="value" data-bind="html: gamma"></div>
</div>
</div>
<script>
let viewer = new Cesium.Viewer('cesiumContainer');
// 1.创建viewModel对象
const viewModel = {
alpha: 1.0, // 透明度, 范围0.0-1.0
brightness: 1.0, // 亮度
contrast: 1.0, // 对比度
hue: 0.0, // 色调
saturation: 1.0, // 饱和度
gamma: 1.0, // 伽马值
};
// 2.监测viewModel中的属性
Cesium.knockout.track(viewModel);
// 3.激活属性, 将viewModel对象与html控件绑定
const inputPanel = document.getElementById('inputPanel');
Cesium.knockout.applyBindings(viewModel, inputPanel);
// 获取当前地球影像
const mLayer = viewer.imageryLayers.get(0);
// 4.监听属性变化
monitorParamChange("alpha");
monitorParamChange("brightness");
monitorParamChange("contrast");
monitorParamChange("hue");
monitorParamChange("saturation");
monitorParamChange("gamma");
function monitorParamChange(name) {
Cesium.knockout.getObservable(viewModel, name).subscribe(function (value) {
// value值改变后会赋值给imagelayer的相应属性
mLayer[name] = value;
});
}
</script>
</body>
</html>
3.2 knockout.js原生用法
完整代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.88/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.88/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html,
body,
#cesiumContainer {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.inputPanel {
position: absolute;
background-color: aliceblue;
top: 20px;
left: 20px;
padding: 10px;
border-radius: 10px;
}
.title {
font-size: 20px;
font-weight: bold;
width: 100%;
text-align: center;
}
.form-item {
display: flex;
align-items: center;
justify-content: center;
margin: 10px;
}
.form-item .label {
width: 80px;
}
.form-item .value {
width: 40px;
}
</style>
<title>Cesium.knockout示例</title>
</head>
<body>
<div id="cesiumContainer"></div>
<div id="inputPanel" class="inputPanel">
<div class="title">图层参数调整</div>
<div class="form-item">
<div class="label">透明度:</div>
<input type="range" min="0.0" max="1.0" step="0.01" data-bind="value: alpha, valueUpdate: 'input'">
<div class="value" data-bind="html: alpha"></div>
</div>
<div class="form-item">
<div class="label">亮度:</div>
<input type="range" min="0.0" max="10" step="0.01" data-bind="value: brightness, valueUpdate: 'input'">
<div class="value" data-bind="html: brightness"></div>
</div>
<div class="form-item">
<div class="label">对比度:</div>
<input type="range" min="0.0" max="10" step="0.01" data-bind="value: contrast, valueUpdate: 'input'">
<div class="value" data-bind="html: contrast"></div>
</div>
<div class="form-item">
<div class="label">色调:</div>
<input type="range" min="0.0" max="6.283" step="0.01" data-bind="value: hue, valueUpdate: 'input'">
<div class="value" data-bind="html: hue"></div>
</div>
<div class="form-item">
<div class="label">饱和度:</div>
<input type="range" min="0.0" max="10" step="0.01" data-bind="value: saturation, valueUpdate: 'input'">
<div class="value" data-bind="html: saturation"></div>
</div>
<div class="form-item">
<div class="label">伽马值:</div>
<input type="range" min="0.0" max="10" step="0.01" data-bind="value: gamma, valueUpdate: 'input'">
<div class="value" data-bind="html: gamma"></div>
</div>
</div>
<script>
let viewer = new Cesium.Viewer('cesiumContainer');
// 1.创建viewModel对象
const viewModel = {
alpha: Cesium.knockout.observable(1.0), // 透明度, 范围0.0-1.0
brightness: Cesium.knockout.observable(1.0), // 亮度
contrast: Cesium.knockout.observable(1.0), // 对比度
hue: Cesium.knockout.observable(0.0), // 色调
saturation: Cesium.knockout.observable(1.0), // 饱和度
gamma: Cesium.knockout.observable(1.0), // 伽马值
};
// 2.激活属性, 将viewModel对象与html控件绑定
const inputPanel = document.getElementById('inputPanel');
Cesium.knockout.applyBindings(viewModel, inputPanel);
// 获取当前地球影像
const mLayer = viewer.imageryLayers.get(0);
// 4.监听属性变化
monitorParamChange("alpha");
monitorParamChange("brightness");
monitorParamChange("contrast");
monitorParamChange("hue");
monitorParamChange("saturation");
monitorParamChange("gamma");
function monitorParamChange(name) {
viewModel[name].subscribe((value) => {
mLayer[name] = value;
});
}
</script>
</body>
</html>
3.3 说明
input标签中绑定数据使用的valueUpdate表示双向绑定的数据在何时更新,valueUpdate: 'input’表示用户输入时更新绑定的数据,对应oninput事件,不添加valueUpdate则使用默认的change事件。