那些移动端web踩过的坑

原文链接:https://geniuspeng.github.io/2017/08/24/mobile-issues/

扔了N久,还是捡回来了。好好弄一下吧。刚工作的时候挺忙的,后来不那么忙了,但是变懒了。
这一年大多数时间都在在做移动端的东东,做了之后才发现,同样是web前端,移动端的坑真的是深不可测,各种各样的,只有想不到,没有遇不到。在这里把最近踩过的坑整理一下。

No.1 大字体问题

首先,要解决的关键问题是如何为设备选择可视视口尺寸,采用理想视口尺寸作为可视视口尺寸,代码也十分简单,只需要将缩放比定为 1

1
<meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">

之所以把它放在第一个也是因为这个印象最深,记得第一次做移动端html5页面的时候,啥都不懂,还在用px作为像素单位,当然这个px是相对的(关于px可参考css 长度单位知多少),然而在移动端使用px是相当不靠谱的,随随便便换一个大小不一样的设备,可能就会出现样式错乱。。。当然后来使用了rem,众所周知1rem=1 htmlFontSize,所以这里只要我们把html的字体大小事先定好,就可以基本上解决字体的适配问题。那么怎么选择这个htmlFontSize呢?
一般UI的设计稿都是3倍大小,这里已1080为例子(我拿到的一般都是1080的),实际上就是360px宽度的device,比如我自己习惯上想让1rem=100px(这里的px是实际像素,相当于设计稿的300像素),那么问题关键就在于,htmlFontSize的多少px正好对应设计稿的300px,那么根元素html的fontsize(设为fz)与设备实际宽度(设为dw)的对应关系应为:

1
2
dw/1080 = fz/300
fz = dw/3.6

这样,你的设备宽度用rem表示其实就是3.6rem,即使不是360px,也可以按比例来缩放,而设计稿中的每300px对应为1rem,使用rem布局,字体大小就可以随着设备宽度来自己调整。

1
2
3
4
5
6
7
8
function setRootFontSize() {
  var r = document.documentElement;
  var dw = r.getBoundingClientRect().width;
  var fz = ~~( dw <= 720 ? dw : 720 ) / 3.6;
  r.style.fontSize = fz + "px";
}
window.addEventListener("resize",setRootFontSize);
setRootFontSize();

这段代码基本上可以满足字体在移动端的自适应要求,可是如果用户主动把设备字体调大,也就是1px对应的大小变大,字体虽然px数不变,显示还是会变大,而有些比较复杂的h5页面字体稍微有些变化就会有问题,所以有时候还需要限定页面的字体大小不随设备字体的调整而影响,这时需要用到getComputedStyle这个方法,getComputedStyle是一个可以获取当前元素所有最终使用的CSS属性值(可参考获取元素CSS值之getComputedStyle方法熟悉)。通过对比算出来的fz和实际的realfz,对其进行相应缩放即可。

1
2
3
4
5
6
7
8
9
10
function setRootFontSize() {
  var r = document.documentElement;
  var dw = r.getBoundingClientRect().width;
  var fz = ~~( dw <= 720 ? dw : 720 ) / 3.6;
  r.style.fontSize = fz + "px";
  var realfz = ~~(+window.getComputedStyle(document.getElementsByTagName("html")[0]).fontSize.replace('px','') * 10000) / 10000;
  if(fz !== realfz){ r.style.fontSize = fz *(fz / realfz) + "px" }
}
window.addEventListener("resize",setRootFontSize);
setRootFontSize();

No.2 滚动时最底层(或顶层)回弹问题

这个坑应该做过移动端的都踩过(只是大多数情况根本不算坑,然而我要做的东西基本上都要禁掉这玩意),试过很多方法,都不是很理想,最终的解决方案就是禁用父级元素的滚动,手动模拟滚动条:

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
var scroll = function(container, childSelector, style) {
  if (!container || !container.length) return;
  if (!(container instanceof $)) {
    container = $(container);
  }
  if (container.css('position') === 'static') {
    container.css('position', 'relative');
  }
 
  var child = container.find(childSelector);
  child.css({
    'transform': 'translate3d(0px, 0px, 0px)',
    '-webkit-transform': 'translate3d(0px, 0px, 0px)'
  });
  var childTop = 0; //距离容器顶部的距离 
  var conHeight = container.height(); //容器高度
  var childHeight = child.height(); //子容器高度
  var diffHeight = conHeight - childHeight; //父子容器高度差
  var clientY = 0; //手指在当前对象上触摸时的坐标
  //添加滚动条
  var defaultStyle = {
    width: '6px',
    position: 'absolute',
    'border-radius': '10000px',
    'background-color': 'rgba(0, 0, 0, 0.3)',
    'pointer-events': 'none',
  };
  $.extend(defaultStyle, style);
  if (!defaultStyle.height) {
    if (diffHeight < 0) {
      defaultStyle.height = conHeight/childHeight * (conHeight);
    } else {
      defaultStyle.height = '0px';
    }
  }
  if (!defaultStyle.top && !defaultStyle.right && !defaultStyle.bottom && !defaultStyle.left) {
    defaultStyle.top = 0;
    defaultStyle.right = 0;
  }
  var scrollBar = $('<section class="scroll-bar ui-scroll-bar"><section>');
  scrollBar.css(defaultStyle);
  container.find('.scroll-bar').remove();
  container.append(scrollBar);


  //绑定前先移除事件
  container.off('touchstart');
  container.off('touchmove');
  container.on('touchstart', function(e) {
    e.preventDefault();
    childTop = child.position().top;
    clientY = e.targetTouches[0].clientY;
  });

  container.on('touchmove', function(e) {
    if (diffHeight > 0) return;
    var curClientY = e.targetTouches[0].clientY;
    var tempTop = childTop + (curClientY - clientY);
    if (tempTop <= diffHeight) {
      tempTop = diffHeight;
    }
    if (tempTop >= 0) {
      tempTop = 0;
    } 
    child.css({
      'transform': 'translate3d(0px, ' + tempTop + 'px, 0px)',
      '-webkit-transform': 'translate3d(0px, ' + tempTop + 'px, 0px)'
    });
    scrollBar.css({
      'transform': 'translate3d(0px, ' + (-tempTop* (conHeight/childHeight)) + 'px, 0px)',
      '-webkit-transform': 'translate3d(0px, ' + (-tempTop* (conHeight/childHeight)) + 'px, 0px)'
    })
  });
}
scroll($('.parent'), '.child')

原理其实还是很简单,先计算一下滚动条的高度,然后按照比例去设置transform(还有很多可优化扩展的地方),第一个参数是父元素的zepto(或jquery)选择器对象,第二个参数是需要滚动的子元素包裹层,第三个是滚动条的样式(可选,有默认),需要把parent元素设置为overflow:hidden,child元素的height设置为auto。

No.3 Modal弹出层滚动带动底层一起滚动

这个也是只有移动端才有的坑,类似与一种穿透吧,手指在弹出层滑动的同时,如果底层有滚动条的话也会跟着一起滑动,解决方案:
css添加:

1
2
3
4
5
/*禁止modal底层滚动*/
body.dialog-open {
  position: fixed;
  width: 100%;
}

js添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var SCROLL_TOP = 0;
/*禁止modal带动底部滚动*/
function to(scrollTop){
  document.body.scrollTop = document.documentElement.scrollTop = scrollTop;
}
function getScrollTop(){
  return document.body.scrollTop || document.documentElement.scrollTop;
}
/*modal弹出时使用*/
SCROLL_TOP = getScrollTop();
document.body.classList.add('dialog-open'); 
document.body.style.top = -SCROLL_TOP + 'px';
/*modal关闭时使用*/
to(SCROLL_TOP);

No.4 tap事件穿透

这算是zepto的一个bug了,具体这个bug的源头还得追到zepto对于tap事件的模拟(参考也来说说touch事件与点击穿透问题),我后来用的解决方法也是参考这里来的,就是使用pointer-events这个属性,这里就不多介绍了,这里的第三种方法,使用fastclick库模拟click也可以完美解决移动端的点穿问题。

1
2
3
4
5
6
7
8
9
10
$('#closePopup').on('tap', function(e){
    $('#popupLayer').hide();
    $('#bgMask').hide();

    $('#underLayer').css('pointer-events', 'none');

    setTimeout(function(){
        $('#underLayer').css('pointer-events', 'auto');
    }, 400);
});
参考链接

文末福利:

福利一:前端,Java,产品经理,微信小程序,Python等10G资源合集大放送:jianshu.com/p/e8197d4d9

福利二:微信小程序入门与实战全套详细视频教程。


【领取方法】

关注 【编程微刊】微信公众号:

回复【小程序demo】一键领取130个微信小程序源码demo资源。

回复【领取资源】一键领取前端,Java,产品经理,微信小程序,Python等资源合集10G资源大放送。



<think>嗯,用户的问题是关于在QGIS中分享地图、项目或数据的各种方法。从引用的资料来看,用户可能已经接触过一些QGIS的基础操作,比如下载天地图数据、使用QGIS2Web插件等,但需要更系统的分享方案整理。用户的核心诉求其实包含三个层次:一是单纯分享静态地图(比如打印或导出图片),二是分享可交互的Web地图,三是分享原始项目文件或数据。这三种需求对应的技术路径完全不同,需要分开说明。对于静态地图分享,用户可能更关注输出质量和便捷性。引用[3]提到的地图集功能特别适合批量生成行政区划图,这对基层工作人员应该很实用。不过要注意提醒用户检查布局中的文字避让和比例尺问题——这是新手常忽略的点。Web地图分享方面,引用[2]的QGIS2Web方案最成熟,但需要重点提醒插件生成的HTML可能在移动端适配不佳。Leaflet和OpenLayers的区别可以补充说明:前者轻量适合简单点位展示,后者更适合复杂GIS分析功能。数据分享部分,用户可能混淆了“导出数据”和“共享项目”的概念。这里需要强调QGS文件仅保存样式和路径引用,实际分享时务必打包相关数据文件。GeoPackage作为现代格式比Shapefile更推荐,但国内很多单位仍要求Shapefile,这个矛盾需要如实告知。突然想到用户没提云平台需求,但QGISCloud的免费版有10MB限制,如果用户想分享高清影像图很可能不够用,这个隐患要提前预警。另外补充了FTP/网盘这类非专业但很实用的方法,毕竟很多国内项目交流还是靠百度网盘。最后列出的几个注意事项都是血泪教训——尤其是坐标系和字体缺失问题,几乎每个QGIS用户都。特意把字体嵌入方法写详细些,用户遇到中文乱码时肯定需要这个。</think> 在QGIS中分享地图、项目或数据可通过多种方式实现,具体方法根据目标受众和技术需求选择。以下是系统化的操作指南: --- ### **一、分享静态地图(图像/PDF)** 1. **直接导出图像** - 在布局窗口设计好地图后(`项目 → 新建打印布局`) - 点击菜单:`布局 → 导出为图片/PDF` - **优势**:跨平台通用,保留矢量文字和高清分辨率。 2. **批量导出地图集**(引用[3]) - 启用打印布局的`地图集`功能 - 选择覆盖图层(如行政区划)作为迭代依据 - 自动为每个区域生成风格统一的系列地图 - **适用场景**:标准化报告、区域统计图集。 --- ### **二、分享交互式Web地图** 1. **通过QGIS2Web插件生成**(引用[2]) - 安装插件:`插件 → 管理并安装插件 → 搜索 qgis2web` - 配置参数:选择Leaflet或OpenLayers引擎,设置图层可见性和弹出信息 - 导出后生成HTML+JS+CSS文件夹,可直接部署到Web服务器 - **优势**:保留符号化样式,支持缩放/点击查询属性。 2. **导出为Web服务(WMS/WFS)** - 启动QGIS Server或使用第三方服务(如GeoServer) - 通过`项目 → 属性 → QGIS Server`发布项目为OWS服务 - 分享URL给用户,支持在GIS平台中动态加载 - **典型用途**:企业内部数据共享或专业GIS协作。 --- ### **三、分享项目文件与原始数据** | **类型** | **操作方法** | **注意事项** | |----------------|-----------------------------------------------------------------------------|-------------------------------------| | **项目文件 (.qgz)** | `项目 → 保存/另存为` | 需同步发送数据源文件或使用相对路径 | | **地理数据** | 右键图层 → `导出 → 另存为` → 选择格式(GeoPackage/Shapefile/GeoJSON等) | GeoPackage避免碎文件问题,GeoJSON适合Web开发 | | **打包项目** | 使用`处理工具箱 → 打包项目`工具(将项目+数据压缩为.qgz或.zip) | 确保数据路径为相对路径 | | **导出地图包** | 通过`Qtiles插件`生成MBTiles,或`处理工具箱 → 导出地图为HTML`生成单文件交互地图 | MBTiles适用于移动端离线地图 | --- ### **四、云端协作与发布** 1. **QGIS Cloud**(官方云平台) - 安装插件`QGIS Cloud`,注册账号 - 上传项目后生成专属链接,支持在线编辑和实时更新 - **免费限制**:10MB数据/项目,付费扩容。 2. **第三方平台整合** - 导出GeoJSON至Mapbox Studio自定义样式 - 上传TIFF到Geoserver发布WMS - 使用FTP/网盘分享大型数据包。 --- ### **关键技术点总结** 1. **路径问题**:分享前在`项目属性 → 常规`中勾选`存储相对路径` 2. **坐标系**:统一使用WGS84(EPSG:4326)或Web Mercator(EPSG:3856)确保兼容性 3. **字体嵌入**:PDF导出时勾选`将文本导出为路径`避免字体缺失 4. **数据压缩**:大型栅格用`Build Overviews`提速,矢量用`.gpkg`替代`.shp` > **操作示例**:导出天地图道路为GeoJSON(引用[1]) > ```markdown > 1. 添加WFS连接:`http://gisserver.tianditu.gov.cn/TDTService/wfs?tk=您的KEY` > 2. 右键图层 → 导出 → GeoJSON → 设置CRS为EPSG:4326 > ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值