- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我明白这不是严格意义上的代码问题 - 但我还没有完全做到这一点 - 让我解释一下......
我有一个要求,使用户能够在大图像上绘制(作为简单的徒手画线)- 并且能够缩放、平移和捏合(在 iPad 上)。
这让我有点抓狂。我查看了如此多的库、代码示例、产品等,似乎没有任何东西可以满足此要求,即通过(多点触摸)捏合、缩放、平移(单点触摸)绘图。很多 paint.net、签名捕获等,但没有任何东西支持多点触控位。
我尝试过调整各种库来实现我想要的(例如,将 sketch.js 的旧版本与 hammer.js 结合使用),但老实说,我一直在努力。我确实怀疑我将不得不在一天结束时自己编写并使用像 hammer.js(顺便说一句非常好)之类的东西来做手势。
无论如何,以防万一有人遇到了可能满足我需求的图书馆,或者可以为我指明正确的方向,我们将不胜感激。
请随意给我一个艰难的时间来避免自己编码 ;-)
最佳答案
该示例显示使用标准浏览器触摸事件的自定义一触式绘制和 2 点缩放、旋转、平移。
您需要通过文档正文中的 CSS 规则 touch-action: none;
来阻止标准手势,否则它将不起作用。
初始化的指针对象
const pointer = setupPointingDevice(canvas);
处理触摸。使用pointer.count
查看有多少次触摸,第一个触摸点可用pointer.x
、pointer.y
。触摸点数组可以作为 pointer.points[touchNumber]
这是一个位于底部的对象,用于处理 View 。它只是一个二维矩阵,带有一些额外的功能来处理夹点。 view.setPinch(point,point)
以 2 个点作为引用开始捏合。然后 view.movePinch(point,point)
进行更新
View 用于在显示 Canvas 上绘制绘图
Canvas 。要获得世界(绘图坐标),您需要将触摸屏坐标( Canvas 像素)转换为转换后的绘图。使用 view.toWorld(pointer.points[0]);
获取收缩绘图的坐标。
要设置主 Canvas 变换,请使用 view.apply();
人类往往很草率,触摸缩放的界面需要稍微延迟绘制,因为捏合 Action 的 2 次触摸可能不会同时发生。当检测到一次触摸时,应用程序开始记录绘图点。如果在几帧之后没有第二次触摸,那么它会锁定到绘图模式。没有触摸事件丢失。
如果在第一次触摸的几帧内发生第二次触摸,则假定正在使用捏操作。该应用会转储之前的所有绘图点并将模式设置为捏合。
当应用处于绘制或捏合模式时,它们会被锁定,直到检测不到任何触摸。这是为了防止由于草率触摸而导致的不良行为。
该演示仅作为示例。
注意 这不适用于非触摸设备。我抛出一个错误是没有找到触摸。
注意 我只做了最基本的代理检测。 Android、iPhone、iPad 以及任何报告多点触控的设备。
注意 收缩事件通常会导致两点合二为一。此示例未正确处理此类事件。当捏合手势变为单点触摸并转动旋转和缩放时,您应该切换到平移模式。
const U = undefined;
const doFor = (count, callback) => {var i = 0; while (i < count && callback(i ++) !== true ); };
const drawModeDelay = 8; // number of frames to delay drawing just incase the pinch touch is
// slow on the second finger
const worldPoint = {x : 0, y : 0}; // worldf point is in the coordinates system of the drawing
const ctx = canvas.getContext("2d");
var drawMode = false; // true while drawing
var pinchMode = false; // true while pinching
var startup = true; // will call init when true
// the drawing image
const drawing = document.createElement("canvas");
const W = drawing.width = 512;
const H = drawing.height = 512;
const dCtx = drawing.getContext("2d");
dCtx.fillStyle = "white";
dCtx.fillRect(0,0,W,H);
// pointer is the interface to the touch
const pointer = setupPointingDevice(canvas);
ctx.font = "16px arial.";
if(pointer === undefined){
ctx.font = "16px arial.";
ctx.fillText("Did not detect pointing device. Demo terminated.", 20,20);
throw new Error("App Error : No touch found");
}
// drawing functions and data
const drawnPoints = []; // array of draw points
function drawOnDrawing(){ // draw all points on drawingPoint array
dCtx.fillStyle = "black";
while(drawnPoints.length > 0){
const point = drawnPoints.shift();
dCtx.beginPath();
dCtx.arc(point.x,point.y,8,0,Math.PI * 2);
dCtx.fill();
dCtx.stroke();
}
}
// called once at start
function init(){
startup = false;
view.setContext(ctx);
}
// standard vars
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
// main update function
function update(timer){
if(startup){ init() };
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.globalCompositeOperation = "source-over";
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
}
// clear main canvas and draw the draw image with shadows and make it look nice
ctx.clearRect(0,0,w,h);
view.apply();
ctx.fillStyle = "black";
ctx.globalAlpha = 0.4;
ctx.fillRect(5,H,W-5,5)
ctx.fillRect(W,5,5,H);
ctx.globalAlpha = 1;
ctx.drawImage(drawing,0,0);
ctx.setTransform(1,0,0,1,0,0);
// handle touch.
// If single point then draw
if((pointer.count === 1 || drawMode) && ! pinchMode){
if(pointer.count === 0){
drawMode = false;
drawOnDrawing();
}else{
view.toWorld(pointer,worldPoint);
drawnPoints.push({x : worldPoint.x, y : worldPoint.y})
if(drawMode){
drawOnDrawing();
}else if(drawnPoints.length > drawModeDelay){
drawMode = true;
}
}
// if two point then pinch.
}else if(pointer.count === 2 || pinchMode){
drawnPoints.length = 0; // dump any draw points
if(pointer.count === 0){
pinchMode = false;
}else if(!pinchMode && pointer.count === 2){
pinchMode = true;
view.setPinch(pointer.points[0],pointer.points[1]);
}else{
view.movePinch(pointer.points[0],pointer.points[1]);
}
}else{
pinchMode = false;
drawMode = false;
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
function touch(element){
const touch = {
points : [],
x : 0, y : 0,
//isTouch : true, // use to determine the IO type.
count : 0,
w : 0, rx : 0, ry : 0,
}
var m = touch;
var t = touch.points;
function newTouch () { for(var j = 0; j < m.pCount; j ++) { if (t[j].id === -1) { return t[j] } } }
function getTouch(id) { for(var j = 0; j < m.pCount; j ++) { if (t[j].id === id) { return t[j] } } }
function setTouch(touchPoint,point,start,down){
if(touchPoint === undefined){ return }
if(start) {
touchPoint.oy = point.pageX;
touchPoint.ox = point.pageY;
touchPoint.id = point.identifier;
} else {
touchPoint.ox = touchPoint.x;
touchPoint.oy = touchPoint.y;
}
touchPoint.x = point.pageX;
touchPoint.y = point.pageY;
touchPoint.down = down;
if(!down) { touchPoint.id = -1 }
}
function mouseEmulator(){
var tCount = 0;
for(var j = 0; j < m.pCount; j ++){
if(t[j].id !== -1){
if(tCount === 0){
m.x = t[j].x;
m.y = t[j].y;
}
tCount += 1;
}
}
m.count= tCount;
}
function touchEvent(e){
var i, p;
p = e.changedTouches;
if (e.type === "touchstart") {
for (i = 0; i < p.length; i ++) { setTouch(newTouch(), p[i], true, true) }
} else if (e.type === "touchmove") {
for (i = 0; i < p.length; i ++) { setTouch(getTouch(p[i].identifier), p[i], false, true) }
} else if (e.type === "touchend") {
for (i = 0; i < p.length; i ++) { setTouch(getTouch(p[i].identifier), p[i], false, false) }
}
mouseEmulator();
e.preventDefault();
return false;
}
touch.pCount = navigator.maxTouchPoints;
element = element === undefined ? document : element;
doFor(navigator.maxTouchPoints, () => touch.points.push({x : 0, y : 0, dx : 0, dy : 0, down : false, id : -1}));
["touchstart","touchmove","touchend"].forEach(name => element.addEventListener(name, touchEvent) );
return touch;
}
function setupPointingDevice(element){
if(navigator.maxTouchPoints === undefined){
if(navigator.appVersion.indexOf("Android") > -1 ||
navigator.appVersion.indexOf("iPhone") > -1 ||
navigator.appVersion.indexOf("iPad") > -1 ){
navigator.maxTouchPoints = 5;
}
}
if(navigator.maxTouchPoints > 0){
return touch(element);
}else{
//return mouse(); // does not take an element defaults to the page.
}
}
const view = (()=>{
const matrix = [1,0,0,1,0,0]; // current view transform
const invMatrix = [1,0,0,1,0,0]; // current inverse view transform
var m = matrix; // alias
var im = invMatrix; // alias
var scale = 1; // current scale
var rotate = 0;
var maxScale = 1;
const pinch1 = {x :0, y : 0}; // holds the pinch origin used to pan zoom and rotate with two touch points
const pinch1R = {x :0, y : 0};
var pinchDist = 0;
var pinchScale = 1;
var pinchAngle = 0;
var pinchStartAngle = 0;
const workPoint1 = {x :0, y : 0};
const workPoint2 = {x :0, y : 0};
const wp1 = workPoint1; // alias
const wp2 = workPoint2; // alias
var ctx;
const pos = {x : 0,y : 0}; // current position of origin
var dirty = true;
const API = {
canvasDefault () { ctx.setTransform(1, 0, 0, 1, 0, 0) },
apply(){ if(dirty){ this.update() } ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]) },
reset() {
scale = 1;
rotate = 0;
pos.x = 0;
pos.y = 0;
dirty = true;
},
matrix,
invMatrix,
update () {
dirty = false;
m[3] = m[0] = Math.cos(rotate) * scale;
m[2] = -(m[1] = Math.sin(rotate) * scale);
m[4] = pos.x;
m[5] = pos.y;
this.invScale = 1 / scale;
var cross = m[0] * m[3] - m[1] * m[2];
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
},
toWorld (from,point = {}) { // convert screen to world coords
var xx, yy;
if (dirty) { this.update() }
xx = from.x - m[4];
yy = from.y - m[5];
point.x = xx * im[0] + yy * im[2];
point.y = xx * im[1] + yy * im[3];
return point;
},
toScreen (from,point = {}) { // convert world coords to screen coords
if (dirty) { this.update() }
point.x = from.x * m[0] + from.y * m[2] + m[4];
point.y = from.x * m[1] + from.y * m[3] + m[5];
return point;
},
setPinch(p1,p2){ // for pinch zoom rotate pan set start of pinch screen coords
if (dirty) { this.update() }
pinch1.x = p1.x;
pinch1.y = p1.y;
var x = (p2.x - pinch1.x);
var y = (p2.y - pinch1.y);
pinchDist = Math.sqrt(x * x + y * y);
pinchStartAngle = Math.atan2(y, x);
pinchScale = scale;
pinchAngle = rotate;
this.toWorld(pinch1, pinch1R)
},
movePinch(p1,p2,dontRotate){
if (dirty) { this.update() }
var x = (p2.x - p1.x);
var y = (p2.y - p1.y);
var pDist = Math.sqrt(x * x + y * y);
scale = pinchScale * (pDist / pinchDist);
if(!dontRotate){
var ang = Math.atan2(y, x);
rotate = pinchAngle + (ang - pinchStartAngle);
}
this.update();
pos.x = p1.x - pinch1R.x * m[0] - pinch1R.y * m[2];
pos.y = p1.y - pinch1R.x * m[1] - pinch1R.y * m[3];
dirty = true;
},
setContext (context) {ctx = context; dirty = true },
};
return API;
})();
canvas {
position : absolute;
top : 0px;
left : 0px;
z-index: 2;
}
body {
background:#bbb;
touch-action: none;
}
<canvas id="canvas"></canvas>
关于javascript - 在支持多点触控捏合、平移和缩放的 HTML5 Canvas 上绘图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45108732/
我在 div 和 jquery ui slider 中有一个图像列表,当用户滑动栏时,图像应该调整大小/缩放(无论你想怎么调用它),我尝试选择所有图像并更改 css 的宽度和使用 jquery 的高度
我正在制作一张具有缩放和平移功能的世界地图。我在某些城市上画了圆圈,圆圈的半径由数据决定。当鼠标悬停在这些圆圈上时,将出现一个工具提示来显示数据。 代码结构为 //在此选择上调用缩放行为 - 让我们调
我正在使用 jquery UI slider (http://jqueryui.com/slider/)。 我需要对整个站点进行缩放。使用 css 样式在 IE+Chrome im 中缩放 - “缩放
我花了很多时间试图找到一种使用 CGAffineScale 将 View 转换为给定点的方法,包括摆弄 anchor 、在转换前后移动 View 的中心以及全面的谷歌搜索。我知道使用 UIScroll
我希望能够用手指旋转和缩放/缩放我的位图,我已经为此寻找了所有示例项目,但它们都与我的代码不匹配。这是我的 onTouchListener 代码。 public class MyView extend
在我的页面中间,我有一个 div 元素,其中包含一些内容(其他 div、图像等)。 before something inside another something
我一直在尝试使用 google maps API V3 的绘图管理器(绘制矩形)。使用 -webkit-transform 缩放谷歌地图 Canvas (包含 div)后像这样 var transf
这个问题在这里已经有了答案: How to resize the iPhone/iPad Simulator? (12 个答案) 关闭 5 年前。 我正在编写 iOS 应用 我使用 Xcode 6.
这几天一直在研究微服务,我想知道人们是如何着手自动化负载平衡和扩展这些东西的? 我心中有一个特定的场景,我想实现什么,但不确定是否可行,或者我的想法有误。就这样吧…… 假设我有一个由 3 台名为 A、
我正在使用 ffmpeg for android 来制作 mp4 格式的视频。我无法让这个命令在 FFMPEG 中工作,基本上我正在尝试添加两个图像,缩放它们,添加缩放效果,最后将结果连接到一个视频文
使用 OpenGL 我正在尝试绘制我校园的原始 map 。 谁能向我解释一下平移、缩放和旋转通常是如何实现的? 例如,通过平移和缩放,这仅仅是我调整我的视口(viewport)吗?所以我绘制并绘制了构
我需要在 iphone sdk 界面生成器中将按钮旋转 30 度,该怎么做? 最佳答案 您无法在 Interface Builder 中执行此操作,但代码非常简单。 确保您已将 IB 中的按钮连接到
假设默认级别等于“1”,是否可以检测触摸设备的捏合(缩放)级别?原因是我希望根据捏合级别禁用页面元素(显示:无)。 如果可以将其放在一个设置缩放级别值的函数中,那就太好了,例如: var ZOOM =
我正在努力找出并确定如何根据这个例子放大我的鼠标位置。 (https://stackblitz.com/edit/js-fxnmkm?file=index.js) let node, scal
我已将 UIWebView 的“scalesPageToFit”属性设置为 YES。 它正在缩放,但是当页面加载时,内容的字体很小,不捏就无法阅读。我们可以将缩放比例设置为默认值吗? 最佳答案 这更多
我仍在学习 jQuery mobile 的技巧,并且在 data-role="page"上放大和缩小图片/图像时遇到问题。有没有办法使用 jquery mobile 在 iPhone 上的图像上进行捏
给定一组 n 个维度为 d 的向量,存储在 (n,d) 数组中,以及第二组 m 相同维度的向量(存储在 (m,d) 数组中)我想计算向量之间的平方点距离,由大小为 的某个矩阵 A 缩放>(d,d)。
我想知道是否有人可以指出我正确的方向。 我使用 CSS3 过渡创建了缩放效果。将鼠标悬停在该对象上,它会转换为更大的版本。这看起来很棒并且效果很棒,但问题是这种效果在 IE 中不起作用,所以我需要在
mapbox-gl-js 版本:0.38.0在 ionic 2.2.11 上通过 npm repo 使用 正如您在这张 gif 上看到的 https://giphy.com/gifs/ionic-an
所以我花了几个小时在网上搜索帮助,但所有论坛和文档都没有向我正确解释我应该采取的最佳方式。 目前我有一个具有 XML 相对布局的 Activity (背景设置为 map 图片),我想实现多手势缩放功能
我是一名优秀的程序员,十分优秀!