关于viewport引起的微信二维码识别区域偏移的问题讨论与解决

本文探讨了在微信页面中使用iPhone时遇到的二维码长按识别偏移问题。通过逐步排查,发现是由于viewport元标签配置不当导致。最终通过调整CSS解决了偏移问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、问题概述

在开发一个含有二维码的微信页面时,我遇到了这样一个问题:使用iPhone第一次进入该页面时,二维码可以长按识别,但第二次进入时长按无法识别到二维码。安卓机都能识别。

二、我和同事进行了以下尝试:

  1. 移除控制进入条件的脚本,即部分第一次第二次,长按不能识别二维码。暂时排除脚本原因。

  2. 移除二维码所有样式,发现并不是不能识别到二维码而是识别区域发生了偏移。(图1)

  3. 移除所有元素,页面上只留一张二维码,发现识别区域变大。虽然整张图都被识别了但图片外面的区域也会被识别。(图2)

识别区域发生偏移
图1
识别区域覆盖变大
图2
阶段性结论:二维码能被长按识别,但因为某种原因识别区域发生了偏移。

三、进一步尝试:

在网上简单搜索了偏移问题后,我注意到一条关于<meta>标签的,大意如下:

meta标签定义了默认缩放为一倍就能识别,不定义就不能识别。于是我将原来的

<meta name="viewport" content="target-densitydpi=device-dpi, user-scalable=no">

改成了

<meta name="viewport" content="user-scalable=no,width=device-width,initial-scale=1,maximum-scale=1">

虽然样式飞了但二维码识别正常了。
看来问题就出在这里了。经过尝试,我发现:
target-densitydpi=device-dpi和width=device-width是冲突的。加上后者二维码识别正常了,但样式肯定要重新定义,若不加,样式好使,但二维码识别就不正常了。定义样式是小事,但归根结底,发生偏移的原因到底在哪呢?

四、分析

  • 关于适配屏幕的<meta name="viewport">标签

UI设计人员都知道因为pc和移动设备屏幕密度像素的不同在输出视觉稿的时候要标明空间的倍数大小,所谓的@1x、@2x就是这个原因。然后在页面head里写这样一个<meta>标签:

<meta name="viewport" content="user-scalable=no,width=device-width,initial-scale=1,maximum-scale=1">

即:宽度强制转换成设备宽度,默认缩放比例为1,最大缩放比例1,不允许手动缩放。
比如按照iPhone6出的设计稿,在开发的时候空间尺寸就要除以2,iPhone6 plus出的设计稿,尺寸就除以3。具体原理请看图3
但是如果不想进行单位换算,可以用

<meta name="viewport" content="target-densitydpi=device-dpi, user-scalable=no">

即:分辨率转为设备分辨率……(后边都一样)
强制将搭建好的页面适应移动设备的分辨率。原理就好比是将大尺寸的图片缩小显示并不影响清晰度。
这样给设计和前端开发人员都带来很大的方便。既不用设计出标注,也不用切两套控件出来了。

  • 由此产生的问题和猜测

问题就是元素偏移了。。。但我猜测应该是这样的:
可视的页面呗强制“塞”到手机屏幕里,但页面本身仍然是原始大小的(图4)。这样看来,并不是触控区域偏移了而是,可视区域被我们“塞到”了移动设备里。发生偏移的实际上是我们看到的那部分。

适配流程
图3

实际上是可视区域偏移了
图4

五、问题的解决

找到了这个原因,剩下的就是老老实实的,按照实际尺寸修改css了,将所有定义了固定尺寸的元素的宽高,包括字体都除以2,保留所有百分比定义的尺寸。哪里不对改哪里,工作量着实不小。这样搭建出来的页面就是实际大小的,没有经过任何缩放,图片该在哪就是在哪不会有偏移了。

六、不能解释的问题

  1. 为什么该问题只有iPhone存在,或许是因为识别二维码的机制不同,也可能是因为浏览器内核原因,安卓的浏览器比较健壮。

  2. 为什么第一次进入页面的时候没有发生偏移?

欢迎,感谢有类似经历的同行,老师参与讨论,留下您的宝贵经验。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>拍拍印</title> <style> body, html { margin: 0; padding: 0; height: 100%; overflow: hidden; } /* 新增滑动轮播关键样式 */ .container { overflow: hidden; position: relative; width: 100vw; height: 100vh; } .slide-track { display: flex; width: 100%; height: 100vh; transition: transform 1.5s cubic-bezier(0.4, 0, 0.2, 1); /* 平滑过渡效果 */ } .slide { flex: 1 0 100vw; /* 每张占满视口宽度 */ background-size: cover; background-position: center; } /* 无限循环关键技巧) */ .slide-track::before, .slide-track::after { content: ''; flex: 100vw; /* 复制首尾图片实现无缝 */ background-image: inherit; } .content { position: absolute; top: 35.5%; left: 4.2%; display: flex; flex-direction: column; justify-content: center; align-items: center; margin: 0 auto; } .box { display: grid; grid-template-columns: repeat(2, 1fr); /* 两列 */ gap: 36px; margin-left: 22px; margin-top: -20px; } .box.no-margin { margin-top: 0 !important; } .item { background-color: #B13627; border-radius: 50px; width: 455px; height: 512px; } .item2 { background-color: #B13627; border-radius: 40px; width: 455px; margin-top: 20px; } .topDiv { width: 100%; height: 172px; } .divP { width: 70%; height: 86px; background-color: #00000000; color: #ffffff; font-size: 64px; font-weight: 700; letter-spacing: 2px; margin-left: 25px; } .divP2 { width: 240px; height: 48px; background-color: #ffffff; color: #000; font-size: 24px; letter-spacing: 2px; margin-left: 30px; display: flex; align-items: center; justify-content: center; margin-top: -50px; border-radius: 25px } .bottomDiv { position: relative; /* 使子元素的绝对定位相对于此元素 */ height: 307px; /* 确保容器有确定的高度 */ } .butDiv, .butDiv2 { position: absolute; left: 20px; bottom: 20px; /* 放置在底部 */ width: calc(100% - 40px); /* 宽度减去左右边距 */ z-index: 10; /* 确保它在图片上方 */ margin-top: 0; /* 移除之前的margin-top */ } /* 调整报纸头条部分 */ .item .butDiv { margin-top: 0; top: auto; /* 重置顶部位置 */ bottom: 20px; /* 放置在底部 */ margin-left: 0; /* 移除左外边距 */ } /* 修改斑马日报和自助打印的按钮位置 */ .item2 .butDiv2 { position: absolute; width: calc(100% - 40px); bottom: 20px; top: -80px; margin-top: 0; margin-left: 0; } /* 调整按钮在卡片内的位置 */ .button, .button2 { margin-top: -13px; margin-left: 265px; position: static; /* 按钮使用默认定位 */ } /* 调整顶部区域避免重叠 */ .topDiv { margin-top: -30px; /* 适当上移 */ z-index: 5; /* 确保在butDiv之上 */ } /* 调整斑马日报和自助打印高度 */ .item2 { position: relative; /* 确保绝对定位元素有参考点 */ height: 236px; } .bottomDiv img { width: 100%; height: 100%; } .butDiv { width: 415px; height: 93px; background-color: #ffffff; border-radius: 47px; margin-top: 185px; margin-left: 20px; } .butDiv2 { width: 415px; height: 93px; background-color: #ffffff; border-radius: 47px; margin-top: 0px; margin-left: 20px; } .avaiDiv { width: 220px; height: 73px; margin-top: 16px; margin-left: 8px; } .available { font-size: 38px; margin-left: 30px; align-self: center; color: #303030; font-weight: 700; } .sizelabie { font-size: 20px; color: #606060; } .button { width: 140px; height: 73px; background-color: #000; border-radius: 50px; margin-top: -60px; margin-left: 255px; display: flex; align-items: center; justify-content: center; color: #ffffff; font-size: 25px; } .button2 { width: 140px; height: 73px; background-color: #000; border-radius: 50px; margin-top: -65px; margin-left: 255px; display: flex; align-items: center; justify-content: center; color: #ffffff; font-size: 25px; } /* 弹窗样式 */ #popup { display: none; } #popup-mask { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.6); z-index: 999; } #popup-content { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1000; background: white; border-radius: 10px; padding: 40px; text-align: center; width: 70%; } #popup-content img { width: 60%; height: 60%; padding: 40px 0; } .popup-header { display: flex; justify-content: space-between; align-items: center; } #popup-close { font-size: 40px; font-weight: bold; color: #000; cursor: pointer; user-select: none; } #countdown { font-size: 26px; color: #000; text-align: right; } .title { text-align: center; font-size: 40px; color: #000; } /* 新增小项目容器样式 */ .small-items-container { display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 20px; } /* 修改小项目宽度为100% */ .item2 { background-color: #B13627; border-radius: 40px; width: 100%; /* 从固定宽度改为100% */ height: 236px; /* 移除原来的margin-bottom */ } </style> </head> <body> <div class="container"> <div class="slide-track"> <div class="slide" style="background-image: url('./assets/bg1.png')"></div> <div class="slide" style="background-image: url('./assets/bg2.png')"></div> <div class="slide" style="background-image: url('./assets/bg1.png')"></div> </div> <div style='background-color:rgba(0, 0, 0, 0);width: 100%;height: 1280px;position: absolute; top: 33.4%;'> <img src="./assets/img.png" style="width: 100%;"> </div> <div class="content"> <div class="box" id="newsContainer"> <!-- <div class="item"> <div class="topDiv"> <p class="divP">报纸头条</p> <p class="divP2">人人都是大明星</p> </div> <div class="bottomDiv"> <img src="./assets/group.png" alt=""> <div class="butDiv"> <span >16/18</span> <div class="button">点击体验 </div> </div> </div> --> <!-- <div style="margin-top: -29px;"> <div class="item2"> <div class="topDiv"> <p class="divP">斑马日报</p> </div> <div> <div class="butDiv2"> <span>16/18</span> <div class="button2">点击体验 </div> </div> </div> </div> <div class="item2"> <div class="topDiv"> <p class="divP">自助打印</p> </div> <div> <div class="butDiv2"> <span>16/18</span> <div class="button2">点击体验 </div> </div> </div> </div> </div> --> </div> </div> </div> <!-- 弹窗结构 --> <div id="popup"> <div id="popup-mask"></div> <div id="popup-content"> <div class="popup-header"> <div id="popup-close">×</div> <div id="countdown">倒计时:120 秒</div> </div> <p class="title">微信扫码解锁自助打印</p> <img src="https://files.vchoo.net/memento/pro/34/webresource/0524893c-b50e-43bf-a0a4-0c4593aa16ac.png" alt="二维码" /> </div> </div> </div> <script> let newspaperData = [] // 获取当前页面的URL参数值 function getQueryParam(paramName) { //(去掉开头的'?') const queryString = window.location.search.substring(1); const params = queryString.split('&'); for (const param of params) { const pair = param.split('='); // 对参数名和值进行URL解码 const name = decodeURIComponent(pair[0]); const value = pair.length > 1 ? decodeURIComponent(pair[1]) : ''; if (name === paramName) { return value; } } // 找不到目标参数返回 return 'C0GSLS'; } // 创建API请求函数 async function fetchNewsData() { const codeValue = getQueryParam('terminalCode'); return fetch(`http://192.168.8.117:5175/api/v1/Common/GetModules?terminalCode=${codeValue}`, { method: "get", headers: { "Content-Type": "application/json" }, }) .then(response => response.json()) .then(res => { if (res.code === 200) { // .slice(0, 4) 截取 return res.data.slice(0, 4).map(module => ({ id: module.id, title: module.moduleName, subtitle: module.moduleDesc, available: module.photoOriginalPrice, size: module.printType == 2 ? "A3报纸" : '6寸照片', image: module.coverImage || "./assets/group.png", moduleUrl: `${module.moduleUrl}?terminalCode=${codeValue}&moduleCode=${module.moduleCode}` })); } else { console.log("请求返回错误:", res.message); return []; } }) .catch(error => { console.error('API请求错误:', error); return []; }); } // 优化初始化函数 async function init() { try { // 等待API返回数据 newspaperData = await fetchNewsData(); console.log("获取到的数据:", newspaperData); // 确保在数据获取后再渲染 renderItems(newspaperData); setupEventListeners(); setupJumpLinks(); // 启动轮播图(新增) setupSlider(); } catch (error) { console.error('初始化失败:', error); // 使用空数组渲染结构 renderItems([]); } } document.addEventListener('touchstart', function (event) { if (event.touches.length > 1) { event.preventDefault(); } }, { passive: false }); let countdownTimer = null; function showPopup() { document.getElementById('popup').style.display = 'block'; let timeLeft = 120; const countdownEl = document.getElementById('countdown'); countdownEl.textContent = `倒计时:${timeLeft} 秒`; countdownTimer = setInterval(() => { timeLeft--; countdownEl.textContent = `倒计时:${timeLeft} 秒`; if (timeLeft <= 0) { hidePopup(); } }, 1000); } // 修改后的渲染函数--判断长度渲染逻辑 function renderItems(data) { const container = document.getElementById('newsContainer'); container.innerHTML = ''; // 获取box元素 const boxElement = container.closest('.box'); if (data.length === 0) { // 没有数据时显示提示 container.innerHTML = `<div class="no-data">数据加载中,请稍后...</div>`; return; } // 原有布局渲染逻辑保持不变 if (data.length === 4) { // 当数组长度为4时,添加no-margin类 boxElement.classList.add('no-margin'); // 长度为4时的布局:全部为大盒子(.item) data.forEach(item => { container.appendChild(createItem(item)); }); } else if (data.length === 5) { // 其他情况下移除no-margin类 boxElement.classList.remove('no-margin'); // 长度为5时的布局:前3个大盒子,最后2个小盒子放在第三个大盒子位置上下排列 // 第一列(包含前2个大盒子) const leftCol = document.createElement('div'); leftCol.className = 'layout-column'; leftCol.appendChild(createItem(data[0])); leftCol.appendChild(createItem(data[1])); container.appendChild(leftCol); // 第二列(包含第三个大盒子和小盒子容器) const rightCol = document.createElement('div'); rightCol.className = 'layout-column'; // 添加第三个大项目 rightCol.appendChild(createItem(data[2])); // 创建小项目容器(包含第四和第五个项目) const smallContainer = document.createElement('div'); smallContainer.className = 'small-items-container'; smallContainer.appendChild(createItem2(data[3])); smallContainer.appendChild(createItem2(data[4])); // 添加小项目容器到第二列 rightCol.appendChild(smallContainer); // 将第二列添加到容器 container.appendChild(rightCol); } else { // 通用渲染方案 - 使用网格布局 const gridContainer = document.createElement('div'); gridContainer.className = 'box'; gridContainer.style.display = 'grid'; gridContainer.style.gridTemplateColumns = 'repeat(2, 1fr)'; gridContainer.style.gap = '36px'; data.forEach(item => { // 根据位置决定渲染大项目或小项目 if (gridContainer.childElementCount < 2) { gridContainer.appendChild(createItem(item)); } else { gridContainer.appendChild(createItem2(item)); } }); container.appendChild(gridContainer); } } function hidePopup() { document.getElementById('popup').style.display = 'none'; if (countdownTimer) clearInterval(countdownTimer); } // 为静态数据添加跳转功能 function setupJumpLinks() { // 为所有按钮添加点击事件 document.querySelectorAll('.print-button').forEach(button => { button.addEventListener('click', function (e) { e.stopPropagation(); // 获取所在项目的data-moduleUrl属性 const container = this.closest('[data-module-url]'); if (container) { const url = container.dataset.moduleUrl; if (url && url !== 'undefined') { window.location.href = url; } else { console.error('无效的跳转URL'); } } }); }); } // 创建大项目(报纸头条) function createItem(item) { const itemElement = document.createElement('div'); itemElement.className = 'item'; itemElement.dataset.id = item.id; itemElement.dataset.moduleUrl = item.moduleUrl; // 添加跳转URL itemElement.innerHTML = ` <div class="topDiv print-button"> <p class="divP">${item.title}</p> <p class="divP2">${item.subtitle}</p> </div> <div class="bottomDiv print-button"> <img src="${item.image}" alt="${item.title}"> <div class="butDiv"> <div class='avaiDiv'> <span class="available"> <span class='sizelabie'>¥</span>${item.available}</span> <span class='sizelabie'> / ${item.size}</span> <div> <span class="button print-button">点击体验</span> </div> </div> `; return itemElement; } // 创建小项目(自助打印) function createItem2(item) { const itemElement = document.createElement('div'); itemElement.className = 'item2'; itemElement.dataset.id = item.id; itemElement.dataset.moduleUrl = item.moduleUrl; // 添加跳转URL itemElement.innerHTML = ` <div class="topDiv print-button"> <p class="divP">${item.title}</p> </div> <div class="bottomDiv print-button"> <div class="butDiv2"> <div class='avaiDiv'> <span class="available"> <span class='sizelabie'>¥</span>${item.available}</span> <span class='sizelabie'> / ${item.size}</span> <div> <div class="button2 print-button">点击体验</div> </div> </div> `; return itemElement; } // 事件监听处理 function setupEventListeners() { document.getElementById('newsContainer').addEventListener('click', function (e) { if (e.target.classList.contains('print-button')) { const itemContainer = e.target.closest('[data-id]'); if (itemContainer) { const itemId = itemContainer.dataset.id; showPopup(); } } }); document.getElementById('popup-mask').onclick = hidePopup; document.getElementById('popup-close').onclick = hidePopup; } // 新增轮播图设置函数 function setupSlider() { const track = document.querySelector('.slide-track'); const slides = document.querySelectorAll('.slide'); let currentIndex = 0; const intervalTime = 5000; // 确保至少有2张幻灯片 if (slides.length < 2) return; function nextSlide() { currentIndex = (currentIndex + 1) % slides.length; track.style.transform = `translateX(-${currentIndex * 100}%)`; if (currentIndex === slides.length - 1) { setTimeout(() => { track.style.transition = 'none'; currentIndex = 0; track.style.transform = 'translateX(0)'; setTimeout(() => { track.style.transition = 'transform 1.5s cubic-bezier(0.4, 0, 0.2, 1)'; }, 50); }, 1500); } } let slideInterval = setInterval(nextSlide, intervalTime); track.addEventListener('mouseenter', () => clearInterval(slideInterval)); track.addEventListener('mouseleave', () => { slideInterval = setInterval(nextSlide, intervalTime); }); } // 移除DOMContentLoaded中的测试按钮代码 document.addEventListener('DOMContentLoaded', init); </script> </body> </html> 把class="divP"的字体改为Alimama ShuHeiTi,其他包涵字体的class的样式字体改为 Alibaba PuHuiTi
最新发布
06-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值