【问题标题】:How to draw responsive lines between divs如何在 div 之间绘制响应线
【发布时间】:2017-02-04 20:18:03
【问题描述】:

这是我正在尝试将其转换为代码的照片。

我还有一些“概念”领域需要布局,我很难找到一种方法来着手以一种响应式工作的方式实现这一目标。我最初的想法是把除了线条之外的所有东西都布置好,这样我就可以根据每个屏幕尺寸调整它们的位置,然后返回并使用媒体查询为元素的每个移位做一组线条来切换它们。对于小型平板电脑和手机尺寸,我想我只是将粗略的草图放大并在线条所指向的每个部分上都有小图标,这会打开一个提供解释的弹出窗口。

到目前为止,我在这里找到了一些帖子,展示了如何使用 SVG 和 Canvas 绘制线条,但我注意到他们提供的示例仅适用于他们设计的窗口大小,当我重新 -调整小提琴窗口的大小,一切都结束了。

我遇到了一些 jsPlumb 受到高度赞扬的帖子,尽管我肯定会开始学习和使用它,但到目前为止,它似乎并不适合我想要用它做的事情,除了事实上,它在源和目标之间创建并维护一条线,无论它们位于何处。我觉得它的可拖动性质对于我想做的事情来说是不必要的,并且无法确定是否有办法打开和关闭它,我不希望人们试图在手机上向下滚动并卡住滑动屏幕周围的框。

事实证明,关于 jsPlumb 的内容确实不多,但仔细研究后,我发现了 GoJS,它看起来与 jsPlumb 非常相似,但再一次,我无法找到关于它的大量信息,或者许多关于人们如何使用它做事的视频。

我遇到了 SVGjs 并对其进行了研究,但从我看到的示例来看,它似乎与我看到的第一个 SVG 和 Canvas 示例几乎相同,它们似乎没有提供灵活性。最重要的是,我现在可以在javascript中“婴儿说话”,所以虽然我可以理解它足以识别我不理解的内容并进行查找,但我不够流利为了跟上我找到的关于这些库的信息的基调,这些库是为那些已经足够了解 JS 的深度而无需更多解释的人编写的。

我知道通常你们更喜欢看我们迄今为止修改的代码示例以解决实际的编码问题,而不是问你如何做某事 n 你是否为我们完成了所有的编码工作,但我'我什至不知道要尝试什么才能做到这一点。所以我希望你们能看到我真的试图以迄今为止我能做到的最好方式来解决这个问题,并且不要用反对票来抨击我,哈哈。我真的不知道从这里去哪里。

【问题讨论】:

  • 你说想要响应式地做,但你只展示了它在相当大屏幕的设备上的样子。例如,您希望它如何在手机上显示?如果只是缩放以适应纵向移动设备的窄屏幕,它看起来会很糟糕。
  • 好吧,您显然只是略读了这篇文章,并没有真正阅读它。这张照片来自我的作品集,用于展示我试图将其转换为响应式代码的内容。你是对的,这在小型设备上看起来很可怕,这是想要让它响应的全部目的,我可以毫无问题地做到这一点。我遇到的问题是找到一种方法来绘制线条并将它们锚定到元素上,以便它们响应它们的变化。
  • 请参阅下面的我的建议,以获得更本土和更轻量级的解决方案。对于像 div 或画布这样的矩形,您将面临的问题是它会覆盖内容并且可能会干扰您的其他元素的可点击性(尽管我想这对您来说可能不是一个大问题)。答案是使用 CSS 旋转在两点之间放置一个线状 div。警告 - 需要男生三角数学。
  • 我记得我的代码在哪里,并在 codepen 上发布了一个工作示例并在下面回答。

标签: javascript html svg html5-canvas


【解决方案1】:

客观地说,您似乎正试图在任意锚定在 HTML 元素中的两点之间绘制多条连接线。在一个类似的项目中,我使用了这个配方:

  • 一个高度为几个像素的透明 div,一个 2 像素宽度的彩色底部边框,并通过 z-index 将其置于其他内容之上。
  • 我计算距离和角度的要连接点的一些三角数学
  • 我用作 CSS div 的宽度和旋转。我还让它不可选择,这样它就不会干扰其他元素的选择/点击。

结果是一个 div,一个底角在 A 点,另一个在 B 点,产生了一条非常漂亮的线,我可以完全控制它。

您需要这样做两次才能获得肘部连接。您可以在页面渲染或重绘等之后触发绘图。

下面 sn-p 中的工作示例 - 可能在全屏下运行得最好,如果你想玩的话,在 codepen http://codepen.io/JEE42/pen/aBOQGj 上运行。

/*
muConnector - a class to create a line joining two elements.

To use, call new with id's of both elements and optonal lineStyle (which must be a valid css border line def such as '1px solid #000' , e.g.
var c1=new Connector(id1, id2, lineStyle)

Default line style is '1px solid #666666'

Whatever you use for drag control, call moved(e, ele) per increment of movement, where e=event and ele=the jq element being moved.

*/

var Connector = function(params) {
if (typeof(params) == "undefined") { return false }; // If no params then abandon.
// Process input params.
var ele1=params.ele1 || '';   // First element to link
var ele2=params.ele2 || '';   // Second element to link
if ( ele1.length === 0 || ele2.length === 0)  { return false }; // If not two element id's then abandon. 

var className=params.class || 'muConnector'

var lineStyle=params.lineStyle || '1px solid #666666';   // CSS style for connector line.

this.gapX1=params.gapX1 || 0;  // First element gap before start of connector, etc
this.gapY1=params.gapY1 || 0;  
this.gapX2=params.gapX2 || 0;
this.gapY2=params.gapY2 || 0;


this.gap=params.gap || 0; // use a single gap setting.
if ( this.gap > 0 ) {
	this.gapX1 = this.gap
	this.gapY1 = this.gap
	this.gapX2 = this.gap
	this.gapY2 = this.gap
}

var pos = function() { // only used for standalone drag processing.
	this.left = 0;
	this.top = 0;
}
		
this.PseudoGuid = new (function() { // Make a GUID to use in unique id assignment - from and credit to http://stackoverflow.com/questions/226689/unique-element-id-even-if-element-doesnt-have-one
	this.empty = "00000000-0000-0000-0000-000000000000";
	this.GetNew = function() {
		var fC = function() { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1).toUpperCase(); }
		return (fC() + fC() + "-" + fC() + "-" + fC() + "-" + fC() + "-" + fC() + fC() + fC());
	};
})();

this.id = this.PseudoGuid.GetNew(); // use guid to avoid id-clashes with manual code.
this.ele1=($('#'+ele1));
this.ele2=($('#'+ele2));

// Append the div that is the link line into the DOM
this.lineID='L' + this.id;
$('body').append("<div id='" + this.lineID + "' class='" + className + "' style=  ></div>")
this.line = $('#L'+ this.id);
this.line.css({ position: 'absolute', 'border-left': this.lineStyle, 'z-index': -100 }) 

// We may need to store the offsets of each element that we are connecting.
this.offsets=[];
this.offsets[ele1]=new pos;
this.offsets[ele2]=new pos;

this.link(); // show the initial link
}

/*
Approach: draw a zero width rectangle where the top left is located at the centre of ele1. 
Compute and make rectangle height equal to the distance between centres of ele1 and ele2.
Now rotate the rectangle to the angle between the points.
Note tracks the edges of the elements so as not to overlay / underlay.
Also can accommodate a gap between edge of element and start of line.

*/	
Connector.prototype.link=function link() {

	var originX = this.ele1.offset().left + this.ele1.outerWidth() / 2;
	var originY = this.ele1.offset().top + this.ele1.outerHeight() / 2;
  
	var targetX = this.ele2.offset().left + this.ele2.outerWidth() / 2;
	var targetY = this.ele2.offset().top + this.ele2.outerHeight() / 2;
	
	var l = this.hyp((targetX - originX),(targetY - originY));
	var angle = 180 / 3.1415 * Math.acos((targetY - originY) / l);
	if(targetX > originX) { angle = angle * -1 }
	
	// Compute adjustments to edge of element plus gaps.
	var adj1=this.edgeAdjust(angle, this.gapX1 + this.ele1.width() / 2, this.gapY1 + this.ele1.height() / 2)
	var adj2=this.edgeAdjust(angle, this.gapX2 + this.ele2.width() / 2, this.gapY2 + this.ele2.height() / 2)

 
	l = l - ( adj1.hp + adj2.hp)
	
	this.line.css({ left: originX, height: l, width: 0, top: originY +  adj1.hp })
		.css('-webkit-transform', 'rotate(' + angle + 'deg)')
		.css('-moz-transform', 'rotate(' + angle + 'deg)')
		.css('-o-transform', 'rotate(' + angle + 'deg)')
		.css('-ms-transform', 'rotate(' + angle + 'deg)')
		.css('transform', 'rotate(' + angle + 'deg)')
		.css('transform-origin', '0 ' + ( -1 * adj1.hp) + 'px');

}

Connector.prototype.Round = function(value, places) {
    var multiplier = Math.pow(10, places);
    return (Math.round(value * multiplier) / multiplier);
}
	
Connector.prototype.edgeAdjust = function (a, w1, h1) {
	var w=0, h=0
	
	// compute corner angles
	var ca=[]
	ca[0]=Math.atan(w1/h1) * 180 / 3.1415926 // RADIANS !!!
	ca[1]=180 - ca[0]
	ca[2]=ca[0] + 180
	ca[3]=ca[1] + 180
	
	// Based on the possible sector and angle combinations work out the adjustments.
	if ( (this.Round(a,0) === 0)  ) {
		h=h1
		w=0
	}
	else if ( (this.Round(a,0) === 180)  ) {
		h=h1
		w=0
	}
	else if ( (a > 0 && a <= ca[0]) || (a < 0 && a >= (-1*ca[0]))  ) {
		h=h1
		w=-1 * Math.tan(a * ( 3.1415926 / 180)) * h1
	}
	
	else if (  a > ca[0] && a <= 90 ) {
		h=Math.tan((90-a) * ( 3.1415926 / 180)) * w1
		w=w1
	}
	else if (  a > 90 && a <= ca[1]  ) {
		h=-1 * Math.tan((a-90) * ( 3.1415926 / 180)) * w1
		w=w1
	}
	else if (  a > ca[1] && a <= 180  ) {
		h=h1
		w=-1 * Math.tan((180 - a) * ( 3.1415926 / 180)) * h1
	}
	else if (  a > -180 && a <= (-1 * ca[1])  ) {
		h=h1
		w= Math.tan((a - 180) * ( 3.1415926 / 180)) * h1
	}	
	else if (  a > (-1 * ca[1])  && a <=  0 ) {
		h=Math.tan((a-90) * ( 3.1415926 / 180)) * w1
		w= w1
	}	

	// We now have the width and height offsets - compute the hypotenuse.
	var hp=this.hyp(w, h)
	
	return {hp: hp }	
}		

Connector.prototype.hyp = function hyp(X, Y) {
return Math.abs(Math.sqrt( (X * X) + ( Y * Y) ))
}


Connector.prototype.moved=function moved(e, ele) {
	var id=ele.attr('id');
	this.link()
}





var line;
$( document ).ready(function() {
    console.log( "ready!" );

	var c1=new Connector({ele1: 'a', ele2: 'b', lineStyle: '1px solid red' })
	Setup(c1, 'a');
	Setup(c1, 'b');
	
	var c2=new Connector({ele1: 'a', ele2: 'c', lineStyle: '1px solid red' })
	Setup(c2, 'a');
	Setup(c2, 'c');

	var c3=new Connector({ele1: 'a', ele2: 'd', lineStyle: '1px solid red' })
	Setup(c3, 'a');
	Setup(c3, 'd');
	
	var c4=new Connector({ele1: 'b', ele2: 'c'})
	Setup(c4, 'b');
	Setup(c4, 'c');	

	var c5=new Connector({ele1: 'b', ele2: 'd'})
	Setup(c5, 'b');
	Setup(c5, 'd');	
	
	var c6=new Connector({ele1: 'c', ele2: 'd'})
	Setup(c6, 'c');
	Setup(c6, 'd');


	function Setup(connector, id) {
		var ele=$('#'+id);
		ele.on('mousedown.muConnector', function(e){
			
			//#critical: tell the connector about the starting position when the mouse goes down.
			connector.offsets[id].left=e.pageX - ele.offset().left;
			connector.offsets[id].top=e.pageY - ele.offset().top;
		
			e.preventDefault();
			
			//hook the mouse move			
			ele.on('mousemove.muConnector', function(e){
			   ele.css({left: e.pageX - connector.offsets[id].left, top: e.pageY - connector.offsets[id].top}); //element position = mouse - offset
			  connector.moved(e, ele); // #critical: call the moved() function to update the connector position.
			});
			
			//define mouse up to cancel moving and mouse up, they are recreated each mousedown
			$(document).on('mouseup', function(e){
			  ele.off('mousemove.muConnector');
			  //$(document).off('.muConnector');
			});
			
		});
	}		

});
.thing {
  width: 200px;
  height: 100px;
  background: transparent;
  position: absolute;
  border: 1px solid #666666;
}

.thing span {
  display: block;
  margin-left: 95px;
  margin-top: 40px;
}

.muConnector {
  position: absolute;
  border-left: 1px dashed red;
  z-index: 100;
  -webkit-transform-origin: top left;
  -moz-transform-origin: top left;
  -o-transform-origin: top left;
  -ms-transform-origin: top left;
  transform-origin: top left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Drag any box...</span>

  <div id="a" class='thing' style='left:400px; top: 100px;'>
    <span>o</span>
  </div>
  
  
  <div id="b" class='thing' style='left:600px; top: 300px;'>
    <span>o</span>
  </div>

  <div id="c" class='thing' style='left:400px; top: 500px;'>
    <span>o</span>
  </div>
  
  
  <div id="d" class='thing' style='left:200px; top: 300px;'>
    <span>o</span>
  </div>

【讨论】:

【解决方案2】:

要解决调整大小等所有问题,请使用百分比定位元素并以百分比创建大小。如果您不知道如何使用百分比来定位事物,请使用下面的 javascript:

var 
        window_width = $(window).width(),
        window_height = $(window).height(),
        unique_name,
        parallax_name_list = [],
        parallax_positions = [];

var vtg = {

    pos: function(obj){
        var posType = "offset";
        determineObject(obj, posType, window_width, window_height);
    },

    coord: function(){
        detectObjectCoords();
    },
}   
function determineObject(obj, posType, window_width, window_height){
        if (($("#"+obj).length == 0)&&($("."+obj).length == 0)){
            throw new Error("VTG_SCRIPT_ERROR: Could not find element '"+obj+"'");
            return;
        } else if (($("#"+obj).length == 1)&&($("."+obj).length == 1)){
            throw new Error("VTG_SCRIPT_ERROR: Classnames and IDs cannot not have the same name.");
            return;
        } else {
                detectObjectType(obj, posType, window_width, window_height);
        }
}
function detectObjectType(obj, posType, window_width, window_height){

var object_type;

if ($("#"+obj).length == 1){
    var object_type = "id";
    positionObject(obj, object_type, posType, window_width, window_height);
} else {
    var object_type = "classname";
    positionObject(obj, object_type, posType, window_width, window_height);
}
}
function positionObject(obj, type, posType, window_width, window_height){

var 
    draggable,
    pos = posType,
    idclass,
    type,
    dot = ".",
    name;

if (type == "id"){
    draggable = $("#"+obj).draggable();
    idclass = "#";
    name = idclass + obj;
    unique_name = name;
} else { 
    draggable = $("."+obj).draggable();
    idclass = ".";
    name = idclass + obj;
    unique_name = name;
}
var
    elem = $(name).position(),
    elem_top = elem.top/$(window).height() * 100,
    elem_left = elem.left/$(window).width() * 100;

        if ((isNaN(elem_top))&&(isNaN(elem_left))){
            console.log("%cCould not calculate the position of the requested object!","font-weight: bold; color: #CC181E;");
            return;
        } else {
            console.debug("$('"+name+"').css({ position: 'absolute', top: '"+elem_top+"%', left: '"+elem_left+"%'});"); 
        }
}
function detectObjectCoords( ){

    var 
        elm = $(unique_name).position(),
        coords_top = elm.top/$(window).height() * 100,
        coords_left = elm.left/$(window).width() * 100;

            console.clear();
            console.log("%cUpdating console","color: #34A953;");
            console.debug("$('"+unique_name+"').css({ position: 'absolute', top: '"+coords_top+"%', left: '"+coords_left+"%'});");

    return;
}
function error(msg){
throw new Error ("VTG_SCRIPT_ERROR: " + msg);
}
function checkType(name){
var new_name
        if ($("#" + name).length == 1){
            new_name = "#" + name;
            return new_name;
        } 
        else if ($("." + name).length == 1){
            new_name = "." + name;
            return new_name;
        } else {
            error("The element could not be found.");
            return false;
        }
}
function empty(word){
    if (word == ""){
        return true;
    } else {
        return false;
    }
}

这段代码来自我目前正在开发的框架。它需要 jQuery 库。要使用它,请输入 > vtg.pos("your object class/id");,您应该能够拖动对象,直到刷新页面。对位置满意后,在控制台中输入 vtg.coord(); 并复制返回的代码并将代码粘贴到您的 html/js 文件中。如果您不确定要复制什么,返回的代码应该类似于:

$('#your item').css({ position: 'absolute', top: '30.841007053291534%', left: '42.31259266123054%'});

您可以使用我的代码顶部放置您想要的行

【讨论】:

  • 从代码来看,它看起来像是用于调整容器大小和移动容器。我没有看到任何与绘制线条相关的 svg 或画布。我计划只使用媒体查询来重新调整元素的大小和位置。我也很擅长使用百分比,虽然我通常使用文本容器将高度设置为自动,但我的宽度总是以百分比为单位。
  • @DaMightyOptiq 我把那个代码放在那里,这样你或用户就可以根据自己的喜好定位你想要的行。另外,我发现另一个论坛帖子与在 HTML 上绘制 SVG 有关。 stackoverflow.com/questions/5495952/…
【解决方案3】:

我仍然不清楚你到底想要做什么,但也许 GoJS 简介第二页的第一张图展示了你想要什么:http://gojs.net/latest/intro/buildingObjects.html。那个图是活的!现在该图表不允许用户绘制新的 cmets,但可以在图表的编辑器版本中提供此类功能。

http://gojs.net/latest/samples/comments.html 中的示例还演示了 cmets、气球 cmets 的不同样式。您可以在 http://gojs.net 找到更多示例。

【讨论】:

    猜你喜欢
    • 2021-12-30
    • 1970-01-01
    • 2019-08-15
    • 2013-02-03
    • 2017-03-28
    • 2017-02-18
    • 2018-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多