1 需求描述

绘制物体外框线条盒子 中介绍了绘制物体外框长方体的方法,本文将介绍物体投影到屏幕上的二维外框绘制方法。

  • 点选物体:点击物体,可以选中物体,按住 Ctrl 追加选中,选中的物体设置为红色。
  • 框选物体:拖拽鼠标,屏幕上会出现滑动框,滑动框内的物体会被选中,选中的物体设置为红色。
  • 绘制外边框:给选中的物体绘制外边框(选中框)。

​ 滑动框效果如下:

​ 选中边框效果如下:

​ 本文完整代码见→ Unity3D点选物体、框选物体、绘制外边框

2 需求实现

2.1 场景搭建

1)场景对象

​ 说明:Plane 的 Layer 设置为 Plane (值为 6)。

2)滑动框

​ 拖拽鼠标时,屏幕上会出现滑动框。SlideBox 对象用于显示滑动框,其 Image 组件中,Source Image 设置为红色滑动框 Sprite,去掉 Raycast Target 勾选,Image Type 设置为 Sliced;RectTransform 组件中,Pivot 设置为 (0, 0),Width、Height 都设置为 0。参数设置如下:

​ 滑动框图片如下:

​ 说明:滑动框图片是 png 格式,中间部分都是半透明的,图片导入 Unity 后,需要修改 Texture Type 为 Sprite,Sprite Mode 设置为 Multiple,并且需要在 Sprite Editor 中 编辑 border(九宫格格式),如下:

2)选框

​ 选中物体后,选中的物体边界会显示外边框(选框)。SelectBox 对象用于显示选框,其 Image 组件中,Source Image 设置为黄色外框 Sprite,去掉 Raycast Target 勾选,Image Type 设置为 Sliced;RectTransform 组件中,Pivot 设置为 (0, 0),Width、Height 都设置为 0。参数设置如下:

​ 外框图片如下:

​ 说明:外框图片是 png 格式,并且除了黄色边角,其他部分都是透明的,图片导入 Unity 后,需要修改 Texture Type 为 Sprite,Sprite Mode 设置为 Multiple,并且需要在 Sprite Editor 中 编辑 border(九宫格格式),如下:

2.2 代码

​ EventDetector.cs

using UnityEngine;

public class EventDetector : MonoBehaviour { // 事件检测器
private MyEventType eventType = MyEventType.None; // 事件类型
private MyEventType lastEventType = MyEventType.None; // 上次事件类型
private float scroll; // 滑轮滑动刻度
private bool detecting; // 事件检测中
private Vector3 clickDownMousePos; // 鼠标按下时的坐标
private const float dragThreshold = 1; // 识别为拖拽的鼠标偏移 private void Update() {
detecting = true;
DetectMouseEvent();
DetectScrollEvent();
UpgradeMouseEvent();
detecting = false;
lastEventType = eventType;
} private void DetectMouseEvent() { // 检测鼠标事件
if (Input.GetMouseButtonDown(0)) { // Click Down
eventType = MyEventType.ClickDown;
clickDownMousePos = Input.mousePosition;
} else if (Input.GetMouseButtonUp(0)) {
if (IsDragEvent(eventType)) { // End Drag
eventType = MyEventType.EndDrag;
} else { // Click Up
eventType = MyEventType.ClickUp;
}
} else if (Input.GetMouseButton(0)) {
if (IsDragEvent(eventType)) { // Drag
eventType = MyEventType.Drag;
} else if (Vector3.Distance(clickDownMousePos, Input.mousePosition) > dragThreshold) { // Begin Drag
eventType = MyEventType.BeginDrag;
} else { // Click
eventType = MyEventType.Click;
}
} else {
eventType = MyEventType.None;
}
} private void DetectScrollEvent() { // 检测滑轮事件
if (eventType != MyEventType.None
&& (!IsBeginEvent(eventType) || lastEventType != MyEventType.None && !IsScrollEvent(lastEventType))) {
scroll = 0;
return;
}
float temScroll = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scroll) < float.Epsilon && Mathf.Abs(temScroll) > float.Epsilon) { // Begin Scroll
eventType = MyEventType.BeginScroll;
scroll = temScroll;
} else if (Mathf.Abs(scroll) > float.Epsilon && Mathf.Abs(temScroll) < float.Epsilon) { // End Scroll
eventType = MyEventType.EndScroll;
scroll = temScroll;
} else if (Mathf.Abs(temScroll) > float.Epsilon) { // Scroll
eventType = MyEventType.Scroll;
scroll = temScroll;
} else {
scroll = 0;
}
} private void UpgradeMouseEvent() { // 升级鼠标事件(关联键盘事件)
if (eventType == MyEventType.None) {
return;
}
if (IsBeginEvent(eventType)) {
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) {
AddKeyType("Ctrl");
} else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) {
AddKeyType("Alt");
}
} else {
ContinueKeyType(); // 保持按键事件
}
} public MyEventType EventType() { // 事件类型
if (detecting) {
return lastEventType;
}
return eventType;
} public bool HasClickEvent() { // 是否有点击事件
MyEventType type = EventType();
return IsClickEvent(type);
} public bool HasDragEvent() { // 是否有拖拽事件
MyEventType type = EventType();
return IsDragEvent(type);
} public bool HasScrollEvent() { // 是否有滑轮事件
MyEventType type = EventType();
return IsScrollEvent(type);
} public bool HasCtrlScrollEvent() { // 是否有Ctrl滑轮事件
MyEventType type = EventType();
return type >= MyEventType.BeginCtrlScroll && type <= MyEventType.EndCtrlScroll;
} public bool IsBeginDrag() { // 是否是开始拖拽类型事件
MyEventType type = EventType();
return type == MyEventType.BeginDrag || type == MyEventType.BeginCtrlDrag || type == MyEventType.BeginAltDrag;
} public float Scroll() { // 鼠标滑轮滑动刻度
if (HasScrollEvent()) {
return scroll;
}
return 0;
} private bool IsClickEvent(MyEventType type) { // 是否是点击事件
return type >= MyEventType.ClickDown && type <= MyEventType.CtrlClickUp;
} private bool IsDragEvent(MyEventType type) { // 是否是拖拽事件
return type >= MyEventType.BeginDrag && type <= MyEventType.EndAltDrag;
} private bool IsScrollEvent(MyEventType type) { // 是否是滑轮事件
return type >= MyEventType.BeginScroll && type <= MyEventType.EndCtrlScroll;
} private bool IsBeginEvent(MyEventType type) { // 是否是开始类型事件
return type == MyEventType.ClickDown
|| type == MyEventType.BeginDrag
|| type == MyEventType.BeginCtrlDrag
|| type == MyEventType.BeginAltDrag
|| type == MyEventType.BeginScroll
|| type == MyEventType.BeginCtrlScroll;
} private bool HasCtrlKey(MyEventType type) { // 是否有Ctrl按键事件
return type >= MyEventType.CtrlClickDown && type <= MyEventType.CtrlClickUp
|| type >= MyEventType.BeginCtrlDrag && type <= MyEventType.EndCtrlDrag
|| type >= MyEventType.BeginCtrlScroll && type <= MyEventType.EndCtrlScroll;
} private bool HasAltKey(MyEventType type) { // 是否有Alt按键事件
return type >= MyEventType.BeginAltDrag && type <= MyEventType.EndAltDrag;
} private void ContinueKeyType() { // 保持按键事件
if (HasCtrlKey(lastEventType)) {
AddKeyType("Ctrl");
} else if (HasAltKey(lastEventType)) {
AddKeyType("Alt");
}
} private void AddKeyType(string key) { // 添加按键事件
if ("Ctrl".Equals(key)) {
if (eventType == MyEventType.ClickDown) { // 点击事件
eventType = MyEventType.CtrlClickDown;
} else if (eventType == MyEventType.Click) {
eventType = MyEventType.CtrlClick;
} else if (eventType == MyEventType.ClickUp) {
eventType = MyEventType.CtrlClickUp;
} else if (eventType == MyEventType.BeginDrag) { // 拖拽事件
eventType = MyEventType.BeginCtrlDrag;
} else if (eventType == MyEventType.Drag) {
eventType = MyEventType.CtrlDrag;
} else if (eventType == MyEventType.EndDrag) {
eventType = MyEventType.EndCtrlDrag;
} else if (eventType == MyEventType.BeginScroll) { // 滑轮事件
eventType = MyEventType.BeginCtrlScroll;
} else if (eventType == MyEventType.Scroll) {
eventType = MyEventType.CtrlScroll;
} else if (eventType == MyEventType.EndScroll) {
eventType = MyEventType.EndCtrlScroll;
}
} else if ("Alt".Equals(key)) {
if (eventType == MyEventType.BeginDrag) { // 拖拽事件
eventType = MyEventType.BeginAltDrag;
} else if (eventType == MyEventType.Drag) {
eventType = MyEventType.AltDrag;
} else if (eventType == MyEventType.EndDrag) {
eventType = MyEventType.EndAltDrag;
}
}
}
} public enum MyEventType { // 事件类型
None = 0,
ClickDown = 1,
Click = 2,
ClickUp = 3,
CtrlClickDown = 4,
CtrlClick = 5,
CtrlClickUp = 6,
BeginDrag = 10,
Drag = 11,
EndDrag = 12,
BeginCtrlDrag = 13,
CtrlDrag = 14,
EndCtrlDrag = 15,
BeginAltDrag = 16,
AltDrag = 17,
EndAltDrag = 18,
BeginScroll = 20,
Scroll = 21,
EndScroll = 22,
BeginCtrlScroll = 23,
CtrlScroll = 24,
EndCtrlScroll = 25
}

​ 说明: EventDetector 脚本组件挂在相机下,用于统一管理事件。点选物体(ClickUp / Ctrl + ClickUp)、滑动选框(Drag)、场景变换(Ctrl + Drag / Alt + Drag)都有鼠标事件,这些事件相互冲突,不便于在每个类里都去捕获鼠标和键盘事件,因此需要 EventDetector 统一管理事件。

​ ClickSelect.cs

using System.Collections.Generic;
using UnityEngine; public class ClickSelect : MonoBehaviour { // 点选物体
private EventDetector eventDetector; // 鼠标事件检测器
private List<Transform> targets; // 选中的游戏对象
private List<Transform> loseFocus; // 失焦的游戏对象
private RaycastHit hit; // 碰撞信息 private void Awake() {
targets = new List<Transform>();
loseFocus = new List<Transform>();
eventDetector = Camera.main.GetComponent<EventDetector>();
GameObject.Find("Work").GetComponent<SlideSelect>().targetsChangedHandler += SetTargets;
} private void Update() {
if (eventDetector.EventType() == MyEventType.ClickUp || eventDetector.EventType() == MyEventType.CtrlClickUp) {
Transform hitTrans = GetHitTrans();
if (hitTrans == null || hitTrans.gameObject.layer == LayerMask.NameToLayer("Plane")) { // 未选中物体或点到地面, 全部取消选中
targets.ForEach(obj => loseFocus.Add(obj));
targets.Clear();
}
else if (eventDetector.EventType() == MyEventType.CtrlClickUp) {
if (targets.Contains(hitTrans)) { // Ctrl重复选中, 取消选中
loseFocus.Add(hitTrans);
targets.Remove(hitTrans);
} else { // Ctrl追加选中
targets.Add(hitTrans);
}
} else { // 单选
targets.ForEach(trans => loseFocus.Add(trans));
loseFocus.Remove(hitTrans);
targets.Clear();
targets.Add(hitTrans);
}
UpdateSelectColor();
RectPainter.DrawRect(targets);
}
} private void UpdateSelectColor() { // 更新选中的物体颜色
foreach(var item in loseFocus) {
item.GetComponent<Renderer>().material.color = Color.gray;
}
foreach(var item in targets) {
item.GetComponent<Renderer>().material.color = Color.red;
}
loseFocus.Clear();
} private void SetTargets(List<Transform> targets) { // 框选时触发
this.targets.ForEach(trans => loseFocus.Add(trans));
if (targets == null) {
this.targets.Clear();
} else {
this.targets = targets;
this.targets.ForEach(trans => loseFocus.Remove(trans));
}
UpdateSelectColor();
} private Transform GetHitTrans() { // 获取屏幕射线碰撞的物体
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit)) {
return hit.transform;
}
return null;
}
}

​ 说明:ClickSelect 脚本组件挂在 Work 对象下,用于点选物体。

​ SlideSelect.cs

using System;
using System.Collections.Generic;
using UnityEngine; public class SlideSelect : MonoBehaviour { // 滑动框选物体
public Action<List<Transform>> targetsChangedHandler; // 框选目标改变时的处理器
private EventDetector eventDetector; // 鼠标事件检测器
private RectTransform slideTrans; // 滑动选框
private Vector3 preMousePos; // 鼠标滑动前的位置
private Transform work; // 需要检测是否被框选的物体根对象
private List<Transform> targets; // 框选的目标对象 private void Awake() {
slideTrans = GameObject.Find("Canvas/SlideBox").GetComponent<RectTransform>();
work = GameObject.Find("Work").transform;
eventDetector = Camera.main.GetComponent<EventDetector>();
} private void Update() {
if (eventDetector.EventType() == MyEventType.BeginDrag) {
preMousePos = Input.mousePosition;
} else if (eventDetector.EventType() == MyEventType.EndDrag) {
Rect rect = slideTrans.rect;
rect.position = slideTrans.position;
targets = RectPainter.DrawRect(work, rect);
targetsChangedHandler?.Invoke(targets);
ClearRect();
} else if (eventDetector.EventType() == MyEventType.Drag) {
DrawRect();
}
} private void DrawRect() { // 绘制滑动选框
float minX = Mathf.Min(Input.mousePosition.x, preMousePos.x);
float minY = Mathf.Min(Input.mousePosition.y, preMousePos.y);
slideTrans.position = new Vector3(minX, minY, 0);
Vector3 delta = Input.mousePosition - preMousePos;
slideTrans.sizeDelta = new Vector2(Mathf.Abs(delta.x), Mathf.Abs(delta.y));
} private void ClearRect() { // 清除滑动选框
slideTrans.sizeDelta = Vector2.zero;
}
}

​ 说明:SlideSelect 脚本组件挂在 Work 对象下,用于滑动框选物体。

​ RectDetector.cs

using System;
using System.Collections.Generic;
using UnityEngine; public class RectDetector { // 边框检测器
public static Rect GetRect(List<Transform> targets) { // 获取物体的外边框(包含子对象)
if (targets != null && targets.Count > 0) {
Rect[] rects = new Rect[targets.Count];
for (int i = 0; i < targets.Count; i++) {
rects[i] = GetRect(targets[i]);
}
return GetRect(rects);
}
return new Rect();
} public static Rect GetCurrRect(List<Transform> targets) { // 获取物体的外边框(不包含子对象)
if (targets != null && targets.Count > 0) {
Rect[] rects = new Rect[targets.Count];
for (int i = 0; i < targets.Count; i++) {
rects[i] = GetCurrRect(targets[i]);
}
return GetRect(rects);
}
return new Rect();
} public static Rect GetRect(Transform transform) { // 获取物体外边框(包含子物体)
Rect rect = GetInitRect();
ForAllChildren(transform, trans => {
Rect rect1 = GetCurrRect(trans);
rect.xMin = Mathf.Min(rect.xMin, rect1.xMin);
rect.yMin = Mathf.Min(rect.yMin, rect1.yMin);
rect.xMax = Mathf.Max(rect.xMax, rect1.xMax);
rect.yMax = Mathf.Max(rect.yMax, rect1.yMax);
});
return rect;
} public static Rect GetCurrRect(Transform transform) { // 获取物体外边框(不包含子对象)
Rect rect = GetInitRect();
Vector3[] vertices = GetVertices(transform);
if (vertices != null && vertices.Length > 0) {
for (int i = 0; i < vertices.Length; i++) {
Vector3 screenPos = Camera.main.WorldToScreenPoint(vertices[i]);
rect.xMin = Mathf.Min(rect.xMin, screenPos.x);
rect.yMin = Mathf.Min(rect.yMin, screenPos.y);
rect.xMax = Mathf.Max(rect.xMax, screenPos.x);
rect.yMax = Mathf.Max(rect.yMax, screenPos.y);
}
}
return rect;
} private static Rect GetRect(Rect[] rects) { // 合并一组边框
if (rects == null || rects.Length == 0) {
return new Rect();
}
Rect rect = rects[0];
for (int i = 1; i < rects.Length; i++) {
rect.xMin = Mathf.Min(rect.xMin, rects[i].xMin);
rect.yMin = Mathf.Min(rect.yMin, rects[i].yMin);
rect.xMax = Mathf.Max(rect.xMax, rects[i].xMax);
rect.yMax = Mathf.Max(rect.yMax, rects[i].yMax);
}
return rect;
} private static Rect GetInitRect() { // 获取初始的边框
Rect rect = new Rect();
rect.xMin = float.MaxValue;
rect.yMin = float.MaxValue;
rect.xMax = float.MinValue;
rect.yMax = float.MinValue;
return rect;
} private static Vector3[] GetVertices(Transform transform) { // 获取网格顶点的世界坐标
if (transform.GetComponent<MeshFilter>() == null || transform.GetComponent<MeshFilter>().mesh == null) {
return null;
}
Vector3[] vertices = transform.GetComponent<MeshFilter>().mesh.vertices;
for (int i = 0; i < vertices.Length; i++)
{
vertices[i] = transform.TransformPoint(vertices[i]);
}
return vertices;
} private static void ForAllChildren(Transform transform, Action<Transform> action) { // 对所有子对象执行活动
if (transform == null || action == null) {
return;
}
Transform[] children = transform.GetComponentsInChildren<Transform>();
for(int i = 0; i < children.Length; i++) {
action(children[i]);
}
}
}

​ 说明:RectDetector 通过遍历 mesh 的所有顶点,并将其投射到屏幕上,以计算出物体的屏幕选框大小。

​ RectPainter.cs

using System;
using System.Collections.Generic;
using UnityEngine; public class RectPainter { // 矩形选框渲染器
private const float border = 5; // 矩形选框的边界宽度
private static RectPainter instance; // 单例
private RectTransform selectTrans; // 选框
private List<Transform> targets; // 选中的游戏对象 private RectPainter() {
selectTrans = GameObject.Find("Canvas/SelectBox").transform as RectTransform;
Camera.main.GetComponent<SceneController>().camChangedHandler += DrawRect;
} public static RectPainter GetInstance() { // 获取单例
if (instance == null) {
instance = new RectPainter();
}
return instance;
} public static void DrawRect(List<Transform> targets) { // 绘制被选中物体的外边框(包含子对象)
if (instance != null) {
instance.targets = targets;
Rect rect = RectDetector.GetRect(targets);
instance.DrawRect(rect);
}
} public static void DrawCurrRect(List<Transform> targets) { // 绘制被选中物体的外边框(不包含子对象)
if (instance != null) {
instance.targets = targets;
Rect rect = RectDetector.GetCurrRect(targets);
instance.DrawRect(rect);
}
} public static List<Transform> DrawRect(Transform root, Rect rect) { // 绘制root下面的在rect内的物体的外边框
if (instance != null) {
instance.targets = new List<Transform>();
ForAllChildren(root, trans => {
Rect rect1 = RectDetector.GetCurrRect(trans);
if (ContainsRect(rect, rect1)) {
instance.targets.Add(trans);
}
});
instance.DrawCurrRect();
return instance.targets;
}
return null;
} private void DrawRect() { // 绘制边框(包含子对象)
Rect rect = RectDetector.GetRect(targets);
DrawRect(rect);
} private void DrawCurrRect() { // 绘制边框(不包含子对象)
Rect rect = RectDetector.GetCurrRect(targets);
DrawRect(rect);
} private void DrawRect(Rect rect) { // 绘制边框
selectTrans.position = new Vector3(rect.x - border, rect.y - border, 0);
selectTrans.sizeDelta = new Vector2(rect.width + border * 2, rect.height + border * 2);
} private static bool ContainsRect(Rect rect1, Rect rect2) { // 判断rect1是否包含rect2
if (rect1.width <= 0 || rect1.height <= 0 || rect2.width <= 0 || rect2.height <= 0) {
return false;
}
if (rect2.xMin < rect1.xMin || rect2.yMin < rect1.yMin || rect2.xMax > rect1.xMax || rect2.yMax > rect1.yMax) {
return false;
}
return true;
} private static void ForAllChildren(Transform transform, Action<Transform> action) { // 对所有子对象执行活动
if (transform == null || action == null) {
return;
}
Transform[] children = transform.GetComponentsInChildren<Transform>();
for(int i = 0; i < children.Length; i++) {
action(children[i]);
}
}
}

​ SceneController.cs

using System;
using UnityEngine; public class SceneController : MonoBehaviour {
private EventDetector eventDetector; // 鼠标事件检测器
public Action camChangedHandler; // 相机改变处理器
private Transform cam; // 相机
private float nearPlan; // 近平面
private Vector3 preMousePos; // 上一帧的鼠标坐标 private void Awake() {
cam = Camera.main.transform;
nearPlan = Camera.main.nearClipPlane;
eventDetector = cam.GetComponent<EventDetector>();
} private void Update() { // 更新场景(Ctrl+Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)
if (eventDetector.HasCtrlScrollEvent()) { // 缩放场景
ScaleScene(eventDetector.Scroll());
} else if (eventDetector.IsBeginDrag()) {
preMousePos = Input.mousePosition;
} else if (eventDetector.HasDragEvent()) {
Vector3 offset = Input.mousePosition - preMousePos;
if (eventDetector.EventType() == MyEventType.CtrlDrag) { // 移动场景
MoveScene(offset);
} else if (eventDetector.EventType() == MyEventType.AltDrag) { // 旋转场景
RotateScene(offset);
}
preMousePos = Input.mousePosition;
}
} private void ScaleScene(float scroll) { // 缩放场景
cam.position += cam.forward * scroll;
camChangedHandler?.Invoke();
} private void MoveScene(Vector3 offset) { // 平移场景
cam.position -= (cam.right * offset.x / 100 + cam.up * offset.y / 100);
camChangedHandler?.Invoke();
} private void RotateScene(Vector3 offset) { // 旋转场景
Vector3 rotateCenter = GetRotateCenter(0);
cam.RotateAround(rotateCenter, Vector3.up, offset.x / 3); // 水平拖拽分量
cam.LookAt(rotateCenter);
cam.RotateAround(rotateCenter, -cam.right, offset.y / 5); // 竖直拖拽分量
camChangedHandler?.Invoke();
} private Vector3 GetRotateCenter(float planeY) { // 获取旋转中心
if (Mathf.Abs(cam.forward.y) < Vector3.kEpsilon || Mathf.Abs(cam.position.y) < float.Epsilon)
{
return cam.position + cam.forward * (nearPlan + 1 / nearPlan);
}
float t = (planeY - cam.position.y) / cam.forward.y;
float x = cam.position.x + t * cam.forward.x;
float z = cam.position.z + t * cam.forward.z;
return new Vector3(x, planeY, z);
}
}

​ 说明: SceneController 脚本组件挂在相机下,用于平移、旋转、缩放场景,其原理见→缩放、平移、旋转场景

3 运行效果

​ 点选效果如下:

​ 框选效果如下:

​ 声明:本文转自点选物体、框选物体、绘制外边框

【Unity3D】点选物体、框选物体、绘制外边框的更多相关文章

  1. unity3d结合轮廓显示,实现完整的框选目标(附Demo代码)

    原地址:http://dong2008hong.blog.163.com/blog/static/469688272013111554511948/ 在unity里实现,其实很简单,因为有两个前提:1 ...

  2. [转]结合轮廓显示,实现完整的框选目标(附Demo代码)

    原地址:http://www.cnblogs.com/88999660/articles/2887078.html 几次看见有人问框选物体的做法,之前斑竹也介绍过,用画的框生成的视椎,用经典图形学的视 ...

  3. unity实现框选效果

    思路: 在uinity中既可以将屏幕坐标转换为世界坐标,也可以将世界坐标转换为屏幕坐标.这样的话我们就可以通过判断物体在世界坐标转换为平幕坐标是否在鼠标框选的矩形区域坐标内,来判断物体是否在框选范围. ...

  4. jquery 拖拽,框选的一点积累

    拖拽draggable,框选 selectable,按ctrl多选,临近辅助对齐,从工具栏拖工具  等,和jqueryui的selectable不同,是在一个父div里框选子div(类似框选文件),一 ...

  5. MagicSuggest – Bootstrap 主题的多选组合框

    MagicSuggest 是专为 Bootstrap 主题开发的多选组合框.它支持自定义呈现,数据通过 Ajax 异步获取,使用组件自动过滤.它允许空间免费项目,也有动态加载固定的建议. 您可能感兴趣 ...

  6. 一种在视频OBJECT标签上放置均分四个区域的框选方法

    一般在视频区域中做框样式,作应由视频插件自己来实现,但是出于其它一些原因自己琢磨了一个使用HTML标签来实现框选区域的方法,按照行外应该属于笨方法,虽然有点笨,可能在其他方面有借鉴意义,在这里拿出来跟 ...

  7. ArcGis 中MapControl 框选

    void mCtrl_OnMouseDown(object sender, ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)    ...

  8. Javascript实现鼠标框选元素后拖拽被框选的元素

    之前需要做一个框选元素后拖拽被框选中的元素功能,在网上找资料做了一些修改,基本达到了需要的效果,希望对也需要实现框选后拖拽元素功能的人有用. 页面加载后效果 框选后的内容可以拖拽,如下图: 代码下载

  9. JQ实现复选框的全选反选不选

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. JavaScript实现框选效果

    <html> <head> <title>region</title> <style> body { margin: 0; padding: ...

随机推荐

  1. Laravel - 控制器的session ( 转载 )

    设置路由 //使用session,需要开启session,//session的开始类在/app/Kernel下//protected $middlewareGroups = [// 'web' =&g ...

  2. Linux-文件用户及组管理-chown-chgrp

  3. Nginx长连接学习之二

    Nginx长连接学习之二 背景 距离最开始学习Nginx的长连接已经一年半; 距离最开始学习Linux的TCP内核参数也已经过去了一年. 最近产品再次出现了TCP链接相关的问题. 因为一开始不知道部署 ...

  4. [转帖]龙叔学ES:Elasticsearch XPACK安全认证

    https://juejin.cn/post/7081994919237287950 本文已参与「新人创作礼」活动,一起开启掘金创作之路. Elasticsearch往往存有公司大量的数据,如果安全不 ...

  5. [转帖]grafana配置邮件发送

    grafana的邮件配置文件是/etc/grafana/grafana.ini,新建grafana.ini文件,内容如下. chown 472:472 grafana.ini ############ ...

  6. [转帖]模拟enq: TX - row lock contention争用

    https://www.modb.pro/db/623036 enq: TX - row lock contention它表示一个事务正在等待另一个事务释放被锁定的行.这种等待事件通常发生在并发访问数 ...

  7. vue3动态组件的展示

    需求描述 有些时候,我们需要做这样的处理. 点击A按钮的时候,出现组件A 点击B按钮的时候,出现组件B 点击C按钮的时候,出现组件C 这个时候,我们就可以使用动态组件了 动态组件 <templa ...

  8. [postgres]配置主从异步流复制

    前言 环境信息 IP 角色 操作系统 PostgreSQL版本 192.168.1.112 主库 Debian 12 15.3 192.168.1.113 从库 Debian 12 15.3 配置主从 ...

  9. Vue双向数据绑定原理-下

    Vue双向数据绑定原理-下这一篇文章主要讲解Vue双向数据绑定的原理,主要是通过Object.defineProperty()来实现的,这里我们手写Vue双向数据绑定的原理. 首先我提出一个需求,我的 ...

  10. springboot多模块打包报错问题根因分析:Unable to find main class

    问题背景: 项目结构为springboot多模块,其中有四个模块bean.utils.user.ems,其中user和ems模块为主程序,包含启动类,其他两个模块为其服务,提供依赖 问题分析: 查看u ...