Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 核心基类

  • Control控件篇

    • Control基类介绍
    • 默认Controls控件渲染过程
    • Zoom缩放控件源码分析
    • Rotate旋转控件源码分析
    • ZoomToExtent控件源码分析
    • ZoomSlider滑块缩放控件源码分析
    • ScaleLine比例尺控件源码分析
    • Attribution属性控件源码分析
    • FullScreen全屏控件源码分析
      • 概述
      • 源码分析
        • FullScreen类的实现如下
        • FullScreen类的构造函数
        • FullScreen中全屏函数
        • FullScreen类的主要方法
      • 总结
    • OverviewMap鹰眼控件源码分析
    • MousePosition鼠标位置控件源码分析
  • Geom几何图形篇

  • Layer图层篇

  • Renderer篇

  • Feature篇

  • style样式篇

  • 《Openlayers源码》笔记
  • Control控件篇
东流
2024-12-11
目录

FullScreen全屏控件源码分析

# 概述

本文主要介绍 Openlayers 中FullScreen全屏控件的实现原理,关于全屏 API 可以参考HTML5 全屏讲解 (opens new window)。可以想象 Openlayers 中的全屏也是基于requestFullscreen实现的,关键点是要处理好地图视图在全屏和非全屏两种状态下切换的处理。

# 源码分析

FullScreen类继承于Control类,关于Control类,可以参考Control基类介绍 (opens new window)。

# FullScreen类的实现如下

class FullScreen extends Control {
  constructor(options) {
    options = options ? options : {};

    super({
      element: document.createElement("div"),
      target: options.target,
    });
    this.on;
    this.once;
    this.un;
    this.keys_ = options.keys !== undefined ? options.keys : false;
    this.source_ = options.source;
    this.isInFullscreen_ = false;
    this.boundHandleMapTargetChange_ = this.handleMapTargetChange_.bind(this);
    this.cssClassName_ =
      options.className !== undefined ? options.className : "ol-full-screen";
    this.documentListeners_ = [];
    this.activeClassName_ =
      options.activeClassName !== undefined
        ? options.activeClassName.split(" ")
        : [this.cssClassName_ + "-true"];
    this.inactiveClassName_ =
      options.inactiveClassName !== undefined
        ? options.inactiveClassName.split(" ")
        : [this.cssClassName_ + "-false"];

    const label = options.label !== undefined ? options.label : "\u2922";
    this.labelNode_ =
      typeof label === "string" ? document.createTextNode(label) : label;

    const labelActive =
      options.labelActive !== undefined ? options.labelActive : "\u00d7";
    this.labelActiveNode_ =
      typeof labelActive === "string"
        ? document.createTextNode(labelActive)
        : labelActive;

    const tipLabel = options.tipLabel ? options.tipLabel : "Toggle full-screen";
    this.button_ = document.createElement("button");
    this.button_.title = tipLabel;
    this.button_.setAttribute("type", "button");
    this.button_.appendChild(this.labelNode_);
    this.button_.addEventListener(
      EventType.CLICK,
      this.handleClick_.bind(this),
      false
    );
    this.setClassName_(this.button_, this.isInFullscreen_);

    this.element.className = `${this.cssClassName_} ${CLASS_UNSELECTABLE} ${CLASS_CONTROL}`;
    this.element.appendChild(this.button_);
  }
  handleClick_(event) {
    event.preventDefault();
    this.handleFullScreen_();
  }
  handleFullScreen_() {
    const map = this.getMap();
    if (!map) {
      return;
    }
    const doc = map.getOwnerDocument();
    if (!isFullScreenSupported(doc)) {
      return;
    }
    if (isFullScreen(doc)) {
      exitFullScreen(doc);
    } else {
      let element;
      if (this.source_) {
        element =
          typeof this.source_ === "string"
            ? doc.getElementById(this.source_)
            : this.source_;
      } else {
        element = map.getTargetElement();
      }
      if (this.keys_) {
        requestFullScreenWithKeys(element);
      } else {
        requestFullScreen(element);
      }
    }
  }
  handleFullScreenChange_() {
    const map = this.getMap();
    if (!map) {
      return;
    }
    const wasInFullscreen = this.isInFullscreen_;
    this.isInFullscreen_ = isFullScreen(map.getOwnerDocument());
    if (wasInFullscreen !== this.isInFullscreen_) {
      this.setClassName_(this.button_, this.isInFullscreen_);
      if (this.isInFullscreen_) {
        replaceNode(this.labelActiveNode_, this.labelNode_);
        this.dispatchEvent(FullScreenEventType.ENTERFULLSCREEN);
      } else {
        replaceNode(this.labelNode_, this.labelActiveNode_);
        this.dispatchEvent(FullScreenEventType.LEAVEFULLSCREEN);
      }
      map.updateSize();
    }
  }
  setClassName_(element, fullscreen) {
    if (fullscreen) {
      element.classList.remove(...this.inactiveClassName_);
      element.classList.add(...this.activeClassName_);
    } else {
      element.classList.remove(...this.activeClassName_);
      element.classList.add(...this.inactiveClassName_);
    }
  }
  setMap(map) {
    const oldMap = this.getMap();
    if (oldMap) {
      oldMap.removeChangeListener(
        MapProperty.TARGET,
        this.boundHandleMapTargetChange_
      );
    }

    super.setMap(map);

    this.handleMapTargetChange_();
    if (map) {
      map.addChangeListener(
        MapProperty.TARGET,
        this.boundHandleMapTargetChange_
      );
    }
  }
  handleMapTargetChange_() {
    const listeners = this.documentListeners_;
    for (let i = 0, ii = listeners.length; i < ii; ++i) {
      unlistenByKey(listeners[i]);
    }
    listeners.length = 0;

    const map = this.getMap();
    if (map) {
      const doc = map.getOwnerDocument();
      if (isFullScreenSupported(doc)) {
        this.element.classList.remove(CLASS_UNSUPPORTED);
      } else {
        this.element.classList.add(CLASS_UNSUPPORTED);
      }

      for (let i = 0, ii = events.length; i < ii; ++i) {
        listeners.push(
          listen(doc, events[i], this.handleFullScreenChange_, this)
        );
      }
      this.handleFullScreenChange_();
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

# FullScreen类的构造函数

FullScreen类的构造函数就是创建了一个按钮,然后监听按钮的点击事件,进行全屏和推出全屏的状态切换,以及按钮状态的切换;构造函数的参数可能有如下属性:

  • target:控件的容器,默认为空,将会添加到overlay container中
  • keys:全屏key,默认false
  • source:控件源,默认undefined
  • className:控件类名,默认ol-full-screen
  • activeClassName:控件全屏状态时的类名ol-full-screen-true
  • inactiveClassName:控件非全屏状态时的类名ol-full-screen-false
  • label:控件标签
  • labelActive:控件全屏(激活状态)标签
  • tipLabel:控件hover时显示

除了定义变量接受参数或有默认值外,构造函数还定义了其它一些变量如下

  • this.isInFullscreen_:默认为false,表示当前是否处于全屏状态
  • this.documentListeners_:默认为[],

控件按钮监听的点击事件是handleClick

# FullScreen中全屏函数

  • isFullScreenSupported函数:判断浏览器是否支持全屏 APIrequestFullscreen以及全屏是否可用fullscreenEnabled
function isFullScreenSupported(doc) {
  const body = doc.body;
  return !!(
    body["webkitRequestFullscreen"] ||
    (body.requestFullscreen && doc.fullscreenEnabled)
  );
}
1
2
3
4
5
6
7
  • isFullScreen函数:判断当前是否处于全屏状态
function isFullScreen(doc) {
  return !!(doc["webkitIsFullScreen"] || doc.fullscreenElement);
}
1
2
3
  • requestFullScreen函数:全屏
function requestFullScreen(element) {
  if (element.requestFullscreen) {
    element.requestFullscreen();
  } else if (element["webkitRequestFullscreen"]) {
    element["webkitRequestFullscreen"]();
  }
}
1
2
3
4
5
6
7
  • requestFullScreenWithKeys函数:全屏
function requestFullScreenWithKeys(element) {
  if (element["webkitRequestFullscreen"]) {
    element["webkitRequestFullscreen"]();
  } else {
    requestFullScreen(element);
  }
}
1
2
3
4
5
6
7
  • exitFullScreen函数:调用element.exitFullscreen退出全屏
function exitFullScreen(doc) {
  if (doc.exitFullscreen) {
    doc.exitFullscreen();
  } else if (doc["webkitExitFullscreen"]) {
    doc["webkitExitFullscreen"]();
  }
}
1
2
3
4
5
6
7

# FullScreen类的主要方法

浏览器的标准规范规定全屏 API 需要用户手动才能触发,不能默认开启全屏。因此默认情况下,只有用户点击按钮交互时,才能操作全屏的相关 API。在上面的构造函数中提到点击按钮会触发handleClick_方法,该方法内部就是防止了默认事件以及调用了handleFullScreen_方法。

  • handleFullScreen_方法:handleFullScreen方法先是调用getMap判断map是否存在,若不存在则返回;然后调用map.getOwnerDocument()获取doc元素,该方法会返回地图容器target,若target不存在,则返回document;然后判断doc元素上是否支持全屏api,若不支持,则返回;然后判断当前状态,若是全屏状态,则调用exitFullScreen退出全屏;否则,判断this.source_是否有值,若有值,则将其赋值给element;否则调用map.getTargetElement获取地图容器元素,最后根据this.keys调用requestFullScreenWithKeys或者requestFullScreen方法使element全屏。

  • setClassName_方法:根据当前地图是否是全屏状态设置控件的类名

  • setMap方法:setMap方法在Map类中初始化控件时或者调用addControl方法添加控件后会调用;FullScreen类中该方法会先调用this.getMap获取之前的oldMap,若存在,则移除target类型的boundHandleMapTargetChange_事件,然后调用父类的setMap方法,调用this.handleMapTargetChange_方法,判断参数map是否存在,若存在,则注册target类型的boundHandleMapTargetChange_监听事件,boundHandleMapTargetChange_实际上就是``

  • handleMapTargetChange_方法:处理地图容器target发生改变时的方法;该方法内部会先遍历this.documentListeners中的监听,然后将其置空;再就是调用this.getMap()获取map,然后判断map,若map存在,则判断是否支持全屏,以此设置全屏控件的样式;然后遍历events,['fullscreenchange','webkitfullscreenchange','MSFullscreenChange']添加它们的监听事件handleFullScreenChange_,并将listen方法返回的key值保存在this.documentListeners中,最后调用一次this.handleFullScreenChange_方法

  • handleFullScreenChange_方法:handleFullScreenChange_方法就是判断全屏状态是否发生改变,若发生变化,则调用setClassName_方法修改全屏控件的类名,以及调用dispatchEvent派发enterfullscreen或者leavefullscreen类型的事件;最后调用map.updateSize()方法更新地图的大小。

# 总结

本文主要介绍了 Openlayers 中全屏控件FullScreen的实现,该控件就是基于HTML5 全屏 API实现的,在元素全屏状态切换时更新地图的大小。

编辑 (opens new window)
上次更新: 2024/12/17, 07:46:54
Attribution属性控件源码分析
OverviewMap鹰眼控件源码分析

← Attribution属性控件源码分析 OverviewMap鹰眼控件源码分析→

最近更新
01
GeoJSON
05-08
02
Circle
04-15
03
CircleMarker
04-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式