一、前言
krpano可以理解为一门小型的编程语言,和我们平时用的js相比,思想和逻辑上非常类似的,只是在语法上有所不同,只要大家能够熟悉krpano语法的套路,相信大家开发一款可定制的vr项目是不难的,下面我将以大家所熟悉的javascript语言进行对比,让大家更快的掌握和理解krpano XML语言。当然,和大家一样作为初学者,我只是把我所理解东西的分享给大家,如果有什么不足之处,请提出来,大家一起学习
准备工作:你已经看完krpano中文网初级教程,并对其有了一定的理解,此外,你已经有Sublime Text 工具。
为了方便大家的学习,我自行翻译了一下krpano官网,链接如下:
二、内容
1. 如何在全景图上写一下自定义的东西?比如导航栏
- 首先,大家打开生成的tour.xml,为了更好的理解,可以把tour.xml中的内容都删掉,我们自己重新写。接下来我们先让tour.xml能够运行起来且能够展示出来全景图,代码如下
<krpano version="1.20.7" title="Virtual Tour" debugmode="true">
// 大家可以看到这个action,有自己的姓名"startup",autorun是什么呢?代表自动运行,onstart代表在xml加载开始的时候运行整个action。
<action name="startup" autorun="onstart">
// 这个条件语句只是判断有scene场景就加载,loadscene()就是加载场景的方法。
if(
startscene === null OR!scene[get(startscene)],
copy(startscene,scene['scene_images'].name)
);
//为什么要用get(startscene)呢?在上面if语句中有个copy的方法,他将scene场景中name等于"scene_images"的name属性值复制给了startscene这个变量,那么,我们用的时候就需要通过get(startscene)方法来获取出来,不能直接写startscene,这样是识别不了的.
loadscene(get(startscene));
</action>
//以下就是我们的第一个名为scene_images的场景
<scene name="scene_images" title="区域沙盘" onstart="" thumburl="panos/images.tiles/thumb.jpg" lat="" lng="" heading="">
//view中的这些属性值大家可以去文档中搜索查看,简单理解就是一个视图
<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
// preview就是进入场景的时候需要先展示那个图片,preview.jpg是自动生成的
<preview url="panos/images.tiles/preview.jpg" />
// image就是加载所有的场景图,至于里面的Cube是什么可以在文档中搜索查看
<image>
<Cube url="panos/images.tiles/pano_%s.jpg" />
</image>
</scene>
</krpano>
- 接下来先写出来一个小按钮,代码如下
通俗一点layer可以理解为div,
<layer
name="sanweishapan"
type="text" //类型,分别有container和text两种
css="color:#fff;" //可以写css,但是只适合type=text,其他是无效的
bgalpha="0" // 透明度,类似于css中的opacity
html="三维沙盘" //要展示的文本内容
parent="" // 告诉此处的layer是属于那个父类layer的,为什么要写这个字段呢?假设你有嵌套的layer,如果要隐藏整个layer,有了parent,那么我们就可以直接给最顶层的layer设为visible="true/false",这样他的所有子layer也会统一隐藏.
align="center" //告诉此layer的位置,如果没有指明parent,那么就以整个窗口为主,如果有parent,则相对于他的父类
bgroundedge="50" // 类似于css中的border-radius
bgcolor="0x000000" // 背景颜色,需要16进制,向我们平时用的css颜色是识别不了的,大家可以去网上搜一下16进制颜色对比图,比如我们要更换颜色为白色,可以这样写:
bgcolor="0xFFFFFF"
/>
此时,我们已经会写出来一个小layer了,那么一个长的导航栏还不简单吗,思路和大家写html一样,一个大div包含多个小div就可以了,代码如下
<layer
name="footer"
keep="true"
style="footer_style"
type="container"
align="leftbottom"
bgalpha="0.5"
x="10" y="10"
scalechildren="true"
maskchildren="true"
bgroundedge="50">
<layer name="sanweishapan" type="text" css="color:#fff;" bgalpha="0" html="三维沙盘" parent="footer" align="center"/>
<layer name="yuyingjiangfang" type="text" css="color:#fff;" bgalpha="0" html="语音讲房" parent="footer" align="center"/>
<layer name="yangbanjian" type="text" css="color:#fff;" bgalpha="0" html="样板间" parent="footer" align="center"/>
<layer name="huxingjianshang" type="text" css="color:#fff;" bgalpha="0" html="户型鉴赏" parent="contain3" align="center"/>
</layer>
大家可以看到上面的代码中,多了以下几个属性,分别是keep,style,scalechildren,maskchildren,x,y 那么他们分别是什么意思呢?
1. keep:就是当你设置为true的时候,在进行场景跳转的时候,并不会消失,比如说在我们在一个项目中进行页面跳转的时候,在上一个页面中的某些div并不会消失,而是会持续保留
2. style:大家可以看到在上面的代码中我们写了很多个类似的layer,那么这些layer中肯定有共有的一些属性,那么我们可以把它抽出来,统一放在style中进行管理,"footer_style"只是自定义的名字,<style>可以参考下面代码
<style
name="footer_style"
width="530"
height="46"/>
3.scalechildren:如果一个由父元素分配的子图层/插件元素在当前图层/插件元素将缩放时也会缩放。
4.maskchildren:对超出父元素的子元素进行裁剪,有时你需要点击某个按钮,将导航栏收进去,在点击,缓慢弹出来,这时你就需要用到这个属性,你只需要控制父元素的宽度或者高度类似于css中的动画属性进行变化就可以了,xml中的动画方法是tween(),可在文档中详细查看
- 接下来我们写一个简单的实例,点击一个按钮让他显示,再点击让他隐藏
我们先随便写一个layer,如下
<layer
name="sanweishapan"
group="zhengwupeitao"
type="text"
css="color:#fff;"
bgalpha="0"
html="三维沙盘"
align="center"
onclick="clickHandle()"
/>
我们先想一下思路,在js中我们实现这个功能应该咋写呢?就写个事件监听点击事件,然后将div的display设为none就可以了,此处我们的思路是一样的,那就有如下几个问题需要我们解决:
第一:我们如何监听layer的事件?
第二:如何找到触发事件的layer?
第三:如何更改layer的visible属性?
第一个问题:在xml中监听事件,和js是一样的,比如点击事件可以这样写οnclick="clickHandle(name)",然后在action中写业务逻辑
<action name="clickHandle" arg="name">
第二个问题:每个layer都有自己的name,可以通过他来找出触发的元素,我们先在事件中传参,将触发的元素的name传递过来,然后通过arg来接受,注意两个名字必须是一样的,接下来我们可以处理
trace(name);//你可以先打印一下name的值,看看有没有传过来
debugvar(layer);//此方法将打印layer的详细信息,大家试一下就知道了,layer是一个数组,接下来开始循环
for(set(i,0),i LT layer.count,inc(i),
if(
layer[get(i)].name=== name,
set(hotspot[get(i)].visible,true),
set(hotspot[get(i)].visible,false);
)
);
和
for(int i; i < layer.length;i++){
if(layer[i].name ===name){
layer[i].visible = true;
}else{
layer[i].visible = false;
}
}
对比一下,就很好理解了,大家可以注意到layer中新出现一个group属性,它标明此处的layer是属于哪个分组下面的,如果要批量执行某个代码的时候,可以把上面代码中的name换成group,name所有属于group这个组的layer都会隐藏
</action>
注意:在action中,每行代码必须以分好";"结尾,否则,代码只能运行到当前行,后面的代码将不会执行
2.如何通过热点跳转某个场景?
想要跳转场景肯定是通过事件来触发的,xml中hotspot是热点,通常用于跳转到某个场景。还是和上面一样,先写一个简单的hotspot
<hotspot
name="jiaotong"
group="jiaotong"
default="true"
html="交通配套"
color="0xff2f6f"
onclick="toScene()"
/>
然后在事件中调用加载场景的方法就可以了.
<action name="toScene">
loadscene("某个场景scene的name");
</action>
如果是小项目我们调用这个方法就可以了,就加载当前xml中的某个场景,但是如果项目比较大,我们不能把所有的代码读写在一个xml中吧,肯定是要分好几个xml的,那如果要加载别的xml中的某个场景应该怎么办?直接调用 loadpanoscene(’%VIEWER%/house.xml’,‘scene_2kt’);就可以了,可以在文档中详细查看此方法,%VIEWER%代表tour.xml所在路径
3. xml加载的生命周期
大家可以看到上面代码中有用过onstart,这个就是xml加载的生命周期,xml中具体有哪些呢?大家可以搜索文档中的事件标签,里面有详细的讲解,了解生命周期是很重要的,比如你可以在场景跳转时,在某个xml加载完之前,进行一下关闭导航栏或者是显示动画等操作
4.户型实例,自定义地图,如何实现如下这种场景?
<krpano version="1.20.7" title="house" debugmode="true">
<action name="startups" autorun="onstart">
if(startscenes === null OR !scene[get(startscenes)], copy(startscenes,scene[0].name); );
loadscene(get(startscenes));
trace("加载完成");
</action>
<!-- 自定义地图 -->
<layer
name="mapcontainer"
visible="false"
keep="true"
type="container"
bgcolor="0x000000"
bgalpha="0.5"
align="lefttop" x="0" y="0" width="264" height="264">
<!-- handcursor当鼠标悬浮上面显示手势 -->
<layer name="map" url="skin/b1map.png" align="top" x="4" y="4" width="prop" height="256" handcursor="false" scalechildren="true">
<!-- 雷达遮罩部分 -->
<!-- maskchildren:当设置为true时,父元素之外的所有子元素将被剪切/屏蔽掉。 -->
<layer name="radarmask" type="container" align="lefttop" width="100%" height="100%" maskchildren="true">
<!-- 雷达插件 zoder=1 在开始时为隐藏,只有激活activetespot这个action时才会显示 zorder为叠放次序 数字越大越靠前 -->
<layer
name="radar"
visible="false"
url="%SWFPATH%/plugins/radar.swf" //可去此文件夹查看
alturl="%SWFPATH%/plugins/radar.js"
align="lefttop"
edge="center"
zorder="1"
scale="0.3"
fillcolor="0xFF0000" fillalpha="0.8"
linecolor="0xFF0000" linewidth="0.5" linealpha="0.5"
headingoffset="0"
/>
<!-- 热点 zorder=2,用style来统一处理 所有layer都载入了一个名为spot的style,注意这里spot是由0开始,而不是由1开始的,因此在一般情况下,地图点与场景一一对应,而场景的index是从0开始的,所以我们可以利用这一点提高代码的可读性-->
<layer name="spot0" style="spot" x="113" y="124" />
<layer name="spot1" style="spot" x="94" y="62" />
<layer name="spot2" style="spot" x="55" y="34" />
<layer name="spot3" style="spot" x="13" y="58" />
<!-- 激活的热点 zorder=3 开始时候隐藏,在这里是一个绿色的地图点, 表示当前的场景-->
<layer name="activespot" url="skin/vtourskin_mapspotactive.png" scale="0.5" oy="-17" align="lefttop" edge="center" zorder="3" visible="false"/>
</layer>
</layer>
</layer>
<!-- 地图点中相同的代码,都写在了style里面,修改的时候只需要修改style里面的代码,提高了效率。在onclick里,先是用subtxt得出spot后面的数字,也就是index,然后检查是否点击的热点就是当前场景,因为没有必要点击当前场景的热点又载入一遍,如果是点击其他的热点,则载入其他场景,载入场景的loadscene中利用了之前得到的spotid,这样就不用每次都写场景的名字了。-->
<style
name="spot"
url="skin/vtourskin_mapspot.png"
scale="0.5"
oy="-17"
align="lefttop"
edge="center"
zorder="2"
onclick="activeScene()"
/>
<action name="activeScene">
<!-- 将当前点击的layer的名字截取一下,得到后面的数字,spot1 => 1,然后将该数字和场景的下标进行对比,如果是点击的是当前已展示的scene则不加载,如果是其他的场景,则重新通过loadscene重新加载 -->
subtxt(spotid,get(name),4,2);
if(spotid != scene[get(xml.scene)].index,
loadscene(get(scene[get(spotid)].name),null,MERGE,BLEND(1,easeinoutback));
);
</action>
<action name="activatespot">
trace("hahahha");
<!-- 因此绿色地图点会在每次激活时替换蓝色普通地图点,因此每次激活之前,首先保证所有蓝色地图点是可见的,如果没有下面这个代码,则会使得上一个场景的蓝色地图点消失,这里用的是一个循环语句-->
for(set(i,0),i LT scene.count,inc(i),
txtadd(spotname,'spot',get(i));
set(layer[get(spotname)].visible, true);
);
<!--这样就使得这个activetespot的参数只要一个就可以了-->
trace(get(xml.scene));
<!-- 将当前激活的场景下标与spot和并,得到spot1,在通过合成后的名字得到layer与此名称相同的标记的坐标,然后将此坐标赋给雷达插件 -->
txtadd(spotidnow,'spot',get(scene[get(xml.scene)].index));
trace(get(spotidnow));
copy(layer[radar].x, layer[get(spotidnow)].x);
copy(layer[radar].y, layer[get(spotidnow)].y);
copy(layer[activespot].x, layer[get(spotidnow)].x);
copy(layer[activespot].y, layer[get(spotidnow)].y);
<!-- 将第二个参数赋值到雷达的heading -->
set(layer[radar].heading, %1);
<!-- 显示雷达和绿色激活热点,以及隐藏当前场景的地图点 -->
set(layer[radar].visible, true);
set(layer[activespot].visible, true);
set(layer[get(spotidnow)].visible, false);
</action>
<scene name="scene_2kt" title="2kt" onstart="activatespot(90)" thumburl="panos/2kt.tiles/thumb.jpg" lat="" lng="" heading="">
<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
<preview url="panos/2kt.tiles/preview.jpg" />
<image>
<cube url="panos/2kt.tiles/pano_%s.jpg" />
<mobile>
<cube url="panos/2kt.tiles/mobile_%s.jpg" />
</mobile>
</image>
</scene>
<scene name="scene_b1cf" title="b1cf" onstart="activatespot(90)" thumburl="panos/b1cf.tiles/thumb.jpg" lat="" lng="" heading="">
<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
<preview url="panos/b1cf.tiles/preview.jpg" />
<image>
<cube url="panos/b1cf.tiles/pano_%s.jpg" />
<mobile>
<cube url="panos/b1cf.tiles/mobile_%s.jpg" />
</mobile>
</image>
</scene>
<scene name="scene_b1ct" title="b1ct" onstart="activatespot(90)" thumburl="panos/b1ct.tiles/thumb.jpg" lat="" lng="" heading="">
<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
<preview url="panos/b1ct.tiles/preview.jpg" />
<image>
<cube url="panos/b1ct.tiles/pano_%s.jpg" />
<mobile>
<cube url="panos/b1ct.tiles/mobile_%s.jpg" />
</mobile>
</image>
</scene>
<scene name="scene_b1etf" title="b1etf" onstart="activatespot(90)" thumburl="panos/b1etf.tiles/thumb.jpg" lat="" lng="" heading="">
<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
<preview url="panos/b1etf.tiles/preview.jpg" />
<image>
<cube url="panos/b1etf.tiles/pano_%s.jpg" />
<mobile>
<cube url="panos/b1etf.tiles/mobile_%s.jpg" />
</mobile>
</image>
</scene>
</krpano>
5.如何进行自动浏览?
<layer
name="control_auto"
css="color:#fff;"
type="text"
html="浏览"
bgalpha="0"
align="top"
onclick="autuRateHandle"/>
<action name="autuRateHandle">
if(
autorotate.enabled,
autorotate.stop(),
autorotate.start()
);
</action>
在xml中随便哪个地方加入一下代码,不要加在<scene>中
<autorotate enabled="false" waittime="1" speed="10.0"/>
6.如何与js进行交互?
如果你看到这儿,并且对前面所讲的这些都了解了,那么你一定会发现.这个xml语言好繁琐啊,一点儿都没有js用起来爽,是的,我也是这么想的.
krpano也设定了与js的打交道的api,大家可以想一想,两者之间如果要打交道,如何才能做到呢?肯定是事件.
假设我能够在js中直接响应xml中的action事件,那么我是不是可以直接把前面所说的导航栏写在html里?
这样事件在xml中执行,模板在html中,对于你这种前端大佬来说是不是就很简单了?具体如下:
- 打开我们的首页tour.html.大家可以先看看里面的内容,其实没多少东西,看下面代码
<!DOCTYPE html>
<html>
<head>
<title>krpano - images</title>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="mobile-web-app-capable" content="yes"/>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta http-equiv="x-ua-compatible" content="IE=edge"/>
<style>
html {
height: 100%;
}
body {
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
font-size: 16px;
color: #000000;
background-color: #000000;
}
</style>
</head>
<body>
<script src="tour.js"></script>
<div id="pano" style="width:100%;height:100%;text-align: center;position: relative">
<noscript>
<table style="width:100%;height:100%;">
<tr style="vertical-align:middle;">
<td>
<div style="text-align:center;">ERROR:<br/><br/>Javascript not activated<br/><br/></div>
</td>
</tr>
</table>
</noscript>
<button onclick="fun1()">点击</button>-->
<script>
var krpano = null;
embedpano({
swf: "tour.swf",
consolelog: "true",
xml: "tour.xml",
target: "pano",
html5: "auto",
mobilescale: 1.0,
passQueryParameters: true,
onready : callback //打开这个回调函数即可,名称自定义
});
function callback(krpano_interface){
krpano = krpano_interface;
}
function fun1(){
console.log(krpano)
if(krpano){
krpano.call("frameHandle")
//触发xml中action名称为frameHandle的事件
}
}
</script>
</div>
</body>
</html>
以上只是一个简单的例子,复杂一点的交互,大家可以查看官网examples中的例子右键查看源码即可,地址如下:
https://krpano.com/releases/1.20.9/viewer/examples/javascript-interface/js-api-examples/index.html
- 我们也可以在xml中写js代码,如下
<action name="..." type="Javascript"><![CDATA[
...
Javascript code
...
]]></action>
三、 总结
在前面的代码学习中,大家也可以感受到xml语言并没有很难,如果遇到问题,可以参照你平时使用的代码思想来想问题,想想用原来的语言是怎么完成某个功能的,然后只需要在文档中查找自己所需要的的方法即可。我给大家列举的一些知识,仅仅只是xml语言所需要的一些基础知识,如果要我列举出官网中所有的例子,显然是不可能的,我只是带大家入个门,后面的学习还需要靠大家自己.加油!