whereMouseEvent
が発生したことを、クリックした要素に対する座標で検出したい。どうして?クリックした場所に絶対位置の子要素を追加したいからです。
CSS3変換が存在しないときにそれを検出する方法を知っています(以下の説明を参照)。ただし、CSS3 Transformを追加すると、アルゴリズムが壊れてしまい、修正方法がわかりません。
JavaScriptライブラリを使用していません。プレーンJavaScriptでの動作を理解したいと思います。したがって、「jQueryを使用するだけ」で答えないでください。
ところで、「クリック」だけでなく、すべてのMouseEventで機能するソリューションが必要です。すべてのマウスイベントが同じプロパティを共有していると考えているため、重要ではありません。したがって、すべてのマウスイベントで同じソリューションが機能するはずです。
DOMレベル2仕様 によると、 MouseEvent
には、イベント座標の取得に関連するプロパティがほとんどありません。
screenX
およびscreenY
は画面座標を返します(Originはユーザーのモニターの左上隅です)clientX
およびclientY
は、ドキュメントビューポートに相対的な座標を返します。したがって、クリックされた要素のコンテンツに関連するMouseEvent
の位置を見つけるには、次の計算を行う必要があります。
_ev.clientX - this.getBoundingClientRect().left - this.clientLeft + this.scrollLeft
_
ev.clientX
_は、ドキュメントビューポートに相対的な座標ですthis.getBoundingClientRect().left
は、ドキュメントビューポートに対する要素の位置です。this.clientLeft
_は、要素の境界と内部座標の間の境界線(およびスクロールバー)の量です。this.scrollLeft
_は、要素内のスクロール量ですgetBoundingClientRect()
、 clientLeft
および scrollLeft
は CSSOMモジュールを表示 。
紛らわしい? 次のJavaScriptとHTMLを試してください 。クリックすると、クリックが発生した正確な場所に赤い点が表示されます。このバージョンは「非常にシンプル」で、期待どおりに機能します。
_function click_handler(ev) {
var rect = this.getBoundingClientRect();
var left = ev.clientX - rect.left - this.clientLeft + this.scrollLeft;
var top = ev.clientY - rect.top - this.clientTop + this.scrollTop;
var dot = document.createElement('div');
dot.setAttribute('style', 'position:absolute; width: 2px; height: 2px; top: '+top+'px; left: '+left+'px; background: red;');
this.appendChild(dot);
}
document.getElementById("experiment").addEventListener('click', click_handler, false);
<div id="experiment" style="border: 5px inset #AAA; background: #CCC; height: 400px; position: relative; overflow: auto;">
<div style="width: 900px; height: 2px;"></div>
<div style="height: 900px; width: 2px;"></div>
</div>
_
_#experiment {
transform: scale(0.5);
-moz-transform: scale(0.5);
-o-transform: scale(0.5);
-webkit-transform: scale(0.5);
/* Note that this is a very simple transformation. */
/* Remember to also think about more complex ones, as described below. */
}
_
アルゴリズムは変換を認識しないため、間違った位置を計算します。さらに、Firefox 3.6とChrome 12では結果が異なります。Opera 11.50はChromeと同じように動作します。
この例では、変換のみがスケーリングであるため、スケーリング係数を乗算して正しい座標を計算できます。ただし、任意の変換(スケール、回転、傾斜、平行移動、行列)、さらにはネストされた変換(別の変換された要素内の変換された要素)について考える場合、座標を計算するためのより良い方法が本当に必要です。
あなたが経験している振る舞いは正しく、あなたのアルゴリズムは壊れていません。まず、CSS3トランスフォームは、ボックスモデルに干渉しないように設計されています。
試して説明するには...
要素にCSS3変換を適用する場合。要素は、一種の相対的な配置を想定しています。その点で、周囲の要素は変換された要素の影響を受けません。
例えば横一列に3つのdivがあるとします。スケール変換を適用して、中央のdivのサイズを小さくする場合。周囲のdivは、内側に移動して、変換された要素を占有していたスペースを占有しません。
例: http://jsfiddle.net/AshMokhberi/bWwkC/
そのため、ボックスモデルでは、要素のサイズは実際には変わりません。レンダリングされたサイズの変更のみです。
また、スケール変換を適用しているため、要素の「実際の」サイズは実際には元のサイズと同じであることに留意する必要があります。認識されるサイズのみを変更しています。
説明する..
1000pxの幅のdivを作成し、サイズを1/2に縮小するとします。 divの内部サイズは1000pxであり、500pxではありません。
したがって、ドットの位置は、divの「実際の」サイズに対して正しいです。
説明のために例を変更しました。
説明書
http://jsfiddle.net/AshMokhberi/EwQLX/
したがって、マウスクリックの座標をdivの表示位置に一致させるためには、マウスがウィンドウに基づいて座標を返していること、およびdivオフセットもその「実際の」サイズに基づいていることを理解する必要があります。 。
オブジェクトのサイズはウィンドウに相対的なので、唯一の解決策は、divと同じスケール値でオフセット座標をスケールすることです。
ただし、divのTransform-Originプロパティを設定した場所に基づいて、これは注意を要することがあります。それはオフセットに影響を与えるためです。
こちらをご覧ください。
http://jsfiddle.net/AshMokhberi/KmDxj/
お役に立てれば。
要素がコンテナであり、絶対または相対に配置されている場合、その要素の内側に配置し、親および幅= 1px、高さ= 1pxを基準にして配置し、コンテナの内側に移動し、各移動後にdocument.elementFromPoint(eventを使用します。 clientX、event.clientY)=))))
バイナリ検索を使用して、高速化することができます。ひどいが、それは動作します
最速で。受け入れられた答えは私の3D変換サイトで約40-70ミリ秒かかります。これは通常20未満です( fiddle ):
function getOffset(event,elt){
var st=new Date().getTime();
var iterations=0;
//if we have webkit, then use webkitConvertPointFromPageToNode instead
if(webkitConvertPointFromPageToNode){
var webkitPoint=webkitConvertPointFromPageToNode(elt,new WebKitPoint(event.clientX,event.clientY));
//if it is off-element, return null
if(webkitPoint.x<0||webkitPoint.y<0)
return null;
return {
x: webkitPoint.x,
y: webkitPoint.y,
time: new Date().getTime()-st
}
}
//make full-size element on top of specified element
var cover=document.createElement('div');
//add styling
cover.style.cssText='height:100%;width:100%;opacity:0;position:absolute;z-index:5000;';
//and add it to the document
elt.appendChild(cover);
//make sure the event is in the element given
if(document.elementFromPoint(event.clientX,event.clientY)!==cover){
//remove the cover
cover.parentNode.removeChild(cover);
//we've got nothing to show, so return null
return null;
}
//array of all places for rects
var rectPlaces=['topleft','topcenter','topright','centerleft','centercenter','centerright','bottomleft','bottomcenter','bottomright'];
//function that adds 9 rects to element
function addChildren(elt){
iterations++;
//loop through all places for rects
rectPlaces.forEach(function(curRect){
//create the element for this rect
var curElt=document.createElement('div');
//add class and id
curElt.setAttribute('class','offsetrect');
curElt.setAttribute('id',curRect+'offset');
//add it to element
elt.appendChild(curElt);
});
//get the element form point and its styling
var eltFromPoint=document.elementFromPoint(event.clientX,event.clientY);
var eltFromPointStyle=getComputedStyle(eltFromPoint);
//Either return the element smaller than 1 pixel that the event was in, or recurse until we do find it, and return the result of the recursement
return Math.max(parseFloat(eltFromPointStyle.getPropertyValue('height')),parseFloat(eltFromPointStyle.getPropertyValue('width')))<=1?eltFromPoint:addChildren(eltFromPoint);
}
//this is the innermost element
var correctElt=addChildren(cover);
//find the element's top and left value by going through all of its parents and adding up the values, as top and left are relative to the parent but we want relative to teh wall
for(var curElt=correctElt,correctTop=0,correctLeft=0;curElt!==cover;curElt=curElt.parentNode){
//get the style for the current element
var curEltStyle=getComputedStyle(curElt);
//add the top and left for the current element to the total
correctTop+=parseFloat(curEltStyle.getPropertyValue('top'));
correctLeft+=parseFloat(curEltStyle.getPropertyValue('left'));
}
//remove all of the elements used for testing
cover.parentNode.removeChild(cover);
//the returned object
var returnObj={
x: correctLeft,
y: correctTop,
time: new Date().getTime()-st,
iterations: iterations
}
return returnObj;
}
また、同じページに次のCSSを含めます。
.offsetrect{
position: absolute;
opacity: 0;
height: 33.333%;
width: 33.333%;
}
#topleftoffset{
top: 0;
left: 0;
}
#topcenteroffset{
top: 0;
left: 33.333%;
}
#toprightoffset{
top: 0;
left: 66.666%;
}
#centerleftoffset{
top: 33.333%;
left: 0;
}
#centercenteroffset{
top: 33.333%;
left: 33.333%;
}
#centerrightoffset{
top: 33.333%;
left: 66.666%;
}
#bottomleftoffset{
top: 66.666%;
left: 0;
}
#bottomcenteroffset{
top: 66.666%;
left: 33.333%;
}
#bottomrightoffset{
top: 66.666%;
left: 66.666%;
}
基本的に、要素を9つの正方形に分割し、document.elementFromPoint
。次に、ピクセル内で正確になるまで、それを9つの小さな正方形などに分割します。私はそれを過度にコメントしたことを知っています。受け入れられた答えはこれより数倍遅いです。
編集:さらに高速になり、ユーザーがChromeまたはSafariにいる場合、9セクターの代わりにこのために設計されたネイティブ関数を使用し、LESS THAN 2で一貫して実行できますミリセコンズ!
また、Webkitの場合 webkitConvertPointFromPageToNode メソッドを使用できます。
var div = document.createElement('div'), scale, point;
div.style.cssText = 'position:absolute;left:-1000px;top:-1000px';
document.body.appendChild(div);
scale = webkitConvertPointFromNodeToPage(div, new WebKitPoint(0, 0));
div.parentNode.removeChild(div);
scale.x = -scale.x / 1000;
scale.y = -scale.y / 1000;
point = webkitConvertPointFromPageToNode(element, new WebKitPoint(event.pageX * scale.x, event.pageY * scale.y));
point.x = point.x / scale.x;
point.y = point.y / scale.x;
別の方法は、変換行列を見つけるよりも、その要素の隅に3 divを配置することですが、配置されたコンテナ可能な要素に対してのみ機能します
これは私にとって本当にうまくいくようです
var elementNewXPosition = (event.offsetX != null) ? event.offsetX : event.originalEvent.layerX;
var elementNewYPosition = (event.offsetY != null) ? event.offsetY : event.originalEvent.layerY;
編集:私の答えはテストされていません、WIP、動作するようになったら更新します。
geomtetry-interfaces のポリフィルを実装しています。 DOMPoint.matrixTransform
メソッド次に作成します。つまり、クリック座標を変換された(ネストされている可能性のある)DOM要素にマッピングするには、次のような記述ができる必要があります。
// target is the element nested somewhere inside the scene.
function multiply(target) {
let result = new DOMMatrix;
while (target && /* insert your criteria for knowing when you arrive at the root node of the 3D scene*/) {
const m = new DOMMatrix(target.style.transform)
result.preMultiplySelf(m) // see w3c DOMMatrix (geometry-interfaces)
target = target.parentNode
}
return result
}
// inside click handler
// traverse from nested node to root node and multiply on the way
const matrix = multiply(node)
const relativePoint = DOMPoint(clickX, clickY, 0, 800).matrixTransform(matrix)
relativePoint
は、クリックした要素の表面に相対的なポイントになります。
W3c DOMMatrixは、CSS変換文字列引数を使用して構築できます。これにより、JavaScriptで非常に使いやすくなります。
残念ながら、これはまだ動作していません(Firefoxのみにジオメトリインターフェースの実装があり、ポリフィルはまだCSS変換文字列を受け入れません)。参照: https://github.com/trusktr/geometry-interfaces/blob/7872f1f78a44e6384529e22505e6ca0ba9b30a4d/src/DOMMatrix.js#L15-L18
これを実装し、実用的な例を入手したら、これを更新します。プルリクエストを歓迎します!
編集:値800
はシーンのパースペクティブです。このようなことをしようとするときに、DOMPoint
コンストラクターの4番目の値がこれであるべきかどうかはわかりません。また、preMultiplySelf
とpostMultiplySelf
のどちらを使用すべきかわかりません。少なくとも機能するようになったら(最初は値が間違っている可能性があります)、答えを更新します。
私はこの問題を抱えており、マトリックスの計算を試み始めました。それを中心にライブラリを開始しました: https://github.com/ombr/referentiel
$('.referentiel').each ->
ref = new Referentiel(this)
$(this).on 'click', (e)->
input = [e.pageX, e.pageY]
p = ref.global_to_local(input)
$pointer = $('.pointer', this)
$pointer.css('left', p[0])
$pointer.css('top', p[1])
どう思いますか ?
var p = $( '.divName' );
var position = p.position();
var left = (position.left / 0.5);
var top = (position.top / 0.5);
私は、DOM座標から変換するポリフィルに取り組んでいます。 GeometryUtils APIはまだ利用できません(@see https://drafts.csswg.org/cssom-view/ )。 2014年にlocalToGlobal、globalToLocal、localToLocalなどの座標を変換するための「単純な」コードを作成しました。まだ完成していませんが、機能します:)今後数か月(2017年7月5日)に完成させると思うので、座標変換を達成するためにAPIがまだ必要な場合は試してみてください: https:// github.com/jsidea/jsideajsideaコアライブラリ 。そのまだ安定していません(プレアルファ)。次のように使用できます。
変換インスタンスを作成します。
var transformer = jsidea.geom.Transform.create(yourElement);
変換先のボックスモデル(デフォルト: "border"。後でENUMに置き換えられます):
var toBoxModel = "border";
入力座標が由来するボックスモデル(デフォルト: "border"):
var fromBoxModel = "border";
グローバル座標(ここでは{x:50、y:100、z:0})をローカル空間に変換します。結果のポイントには、x、y、z、wの4つのコンポーネントがあります。
var local = transformer.globalToLocal(50, 100, 0, toBoxModel, fromBoxModel);
LocalToGlobalやlocalToLocalのような他の関数を実装しました。試してみたい場合は、リリースビルドをダウンロードしてjsidea.min.jsを使用してください。
最初のリリースをここからダウンロードします。 TypeScriptコードをダウンロード
コードを自由に変更してください、私は決してライセンスの下にそれを置きません:)