【问题标题】:Speech bubble with arrow带箭头的讲话泡泡
【发布时间】:2015-07-29 17:09:42
【问题描述】:

我有一个项目需要在其中插入语音气泡/消息框。我想要达到的一般形状是这个:

.bubble {
  height: 100px;
  width: 200px;
  border: 3px solid gray;
  background: lightgray;
  position: relative;
  cursor:pointer;
}
.triangle {
  width: 0;
  border-top: 20px solid black;
  border-left: 20px solid transparent;
  border-right: 20px solid transparent;
  cursor:pointer;
}
<div class="bubble">Speech bubble
</div>
<div class="triangle">
</div>

这目前没有通过命中测试,因为透明边框也是可点击的。

目标

  • 点击框(可点击/可悬停区域)需要粘在形状的边界上(这里的透明边框也是可悬停的,这无效)。

  • 我需要在各种内容(图像、渐变、文本...)上显示形状,

问题

我在处理这个形状时遇到的主要问题是:

  • 能够根据所指元素的位置(顶部/左侧/右侧/底部)围绕对话气泡移动三角形
  • 需要强调时在其周围添加边框或框阴影

有没有办法解决这些问题?

【问题讨论】:

  • 这是一个重复的问题
  • @Andrew:为您的评论干杯,然而,我已经在这个标签附近有一段时间了,还没有找到一个关于所有观点的问题。如果您确实遇到这样的问题,请随时链接,我将把我的答案移到那里。

标签: css css-shapes


【解决方案1】:

为了实现这一点,您应该考虑更改标记以使您的 html 更有效率。这可以使用伪元素来实现。我将分别解决每个问题,并在我的回答结束时将它们放在一起。

首先,

使用伪元素避免多余的元素

您可以使用伪元素来删除多余的.triangle div。这不仅减少了您的 div 数量,而且还有助于定位,因为您可以使用 top: left: right:bottom: css 属性来根据您的主要元素进行定位。这可以在下面看到:

.oneAndOnlyDiv {
  height: 100px;
  width: 200px;
  border: 3px solid gray;
  background: lightgray;
  position: relative;
}
.oneAndOnlyDiv:before {
  content: "";
  position: absolute;
  top: 100%;
  left: 20px;
  width: 0;
  border-top: 20px solid black;
  border-left: 20px solid transparent;
  border-right: 20px solid transparent;
}
&lt;div class="oneAndOnlyDiv"&gt;Main div&lt;/div&gt;

命中测试

为了创建您的“命中测试”,您可能希望使用旋转元素而不是边框​​ hack。

类似:

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor:pointer;
}
div:before {
  content: "";
  position: absolute;
  top: 100%;
  left: 20px;
  height: 20px;
  width: 20px;
  background: black;
  transform: rotate(45deg);
  transform-origin:top right;
}
&lt;div&gt;Only element&lt;/div&gt;

或使用倾斜的伪元素:

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor:pointer;
}
div:before {
  content: "";
  position: absolute;
  top: 90%;
  left: 20px;
  height: 30%;
  width: 20px;
  background: black;
  transform: skewY(-45deg);
  transform-origin:bottom left;
  z-index:-1;
}
&lt;div&gt;Only element&lt;/div&gt;

只有当正方形或主元素悬停时才会显示指针。 但是等一下,这会打乱定位吗?怎么办?

对此有一些解决方案。其中之一是使用calc CSS 属性。

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor:pointer;
}
div:before {
  content: "";
  position: absolute;
  top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
  top: calc(100% - 10px); /*i.e. half the height*/
  left: 20px;
  height: 20px;
  width: 20px;
  background: gray;
  transform: rotate(45deg);
}
&lt;div&gt;Only element&lt;/div&gt;

添加边框

你现在可以很容易地添加边框,只需在主元素中添加边框声明,并将伪元素的border-bottomborder-right 设置为inherit

边框

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor:pointer;
  border:3px double black;
}
div:before {
  content: "";
  position: absolute;
  top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
  top: calc(100% - 10px); /*i.e. half the height*/
  left: 20px;
  height: 20px;
  width: 20px;
  background: gray;
  transform: rotate(45deg);
  border-bottom:inherit;
  border-right:inherit;
  box-shadow:inherit;
}
&lt;div&gt;Only element&lt;/div&gt;

盒子阴影:

为了有一个盒子阴影,我使用了:after 伪元素,以便将盒子阴影隐藏在另一个伪元素上,使元素看起来像一个单独的元素。

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor:pointer;
  box-shadow: 5px 5px 10px 2px black;
}
div:before,div:after {
  content: "";
  position: absolute;
  top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
  top: calc(100% - 10px); /*i.e. half the height*/
  left: 20px;
  height: 20px;
  width: 20px;
  background: gray;
  transform: rotate(45deg);
z-index:-1;
  box-shadow:inherit;
}
div:after{
  box-shadow:none;
  z-index:8;
  }
&lt;div&gt;Only element&lt;/div&gt;

把它们放在一起

您还可以使用border-radius 属性再次为消息框或语音气泡添加边框半径:

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor:pointer;
  border:3px double black;
  border-radius:10px;
}
div:before {
  content: "";
  position: absolute;
  top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
  top: calc(100% - 10px); /*i.e. half the height*/
  left: 20px;
  height: 20px;
  width: 20px;
  background: gray;
  transform: rotate(45deg);
  border-bottom:inherit;
  border-right:inherit;
  box-shadow:inherit;
}
&lt;div&gt;Only element&lt;/div&gt;

这甚至可以让您不仅创建一个三角形,还可以创建一个圆形呢?

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor:pointer;
  border:3px double black;
  border-radius:10px;
}
div:before {
  content: "";
  position: absolute;
  top: -webkit-calc(100% - 13px); /*may require prefix for old browser support*/
  top: calc(100% - 13px); /*i.e. half the height + border*/
  left: 20px;
  height: 20px;
  width: 20px;
  background: gray;
  transform: rotate(45deg);
  border:3px double transparent;
  border-bottom:inherit;
  border-right:inherit;
  box-shadow:inherit;
  border-radius:50%;
}
&lt;div&gt;Only element&lt;/div&gt;

如果您遇到内容溢出并被“隐藏”在此伪元素后面的问题,并且您不担心有边框,则可以使用负 z-index 来解决此问题。

不喜欢使用“幻数”?

如果您不喜欢使用 calc 值的想法,我的答案中的定位当前正在使用(虽然工作),您可能希望使用 transform:translate(50%)

这将是一个更好的方法,因为:

  • 您不需要知道边框的大小,也不需要知道宽度的一半
  • 您将使消息框/气泡的位置更加动态化,并支持进一步调整大小。

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor: pointer;
  border: 3px double black;
  border-radius: 10px;
}
div:before {
  content: "";
  position: absolute;
  top: 100%;
  left: 30px;
  height: 20px;
  width: 20px;
  background: gray;
  box-sizing:border-box;
  transform: rotate(45deg) translate(-50%);
  border-bottom: inherit;
  border-right: inherit;
  box-shadow: inherit;
}
&lt;div&gt;Only element&lt;/div&gt;

要移动它吗?你可以!

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor: pointer;
  border: 3px double black;
  border-radius: 10px;
}
div:before {
  content: "";
  position: absolute;
  top: 100%;
  left: 10%;
  height: 20px;
  width: 20px;
  background: gray;
  box-sizing: border-box;
  transform: rotate(45deg) translate(-50%);
  border-bottom: inherit;
  border-right: inherit;
  box-shadow: inherit;
  transition: all 0.8s;
}
div:hover:before {
  left: 90%;
}
&lt;div&gt;Only element&lt;/div&gt;

想要一个合适的吗?

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor: pointer;
  border: 3px double black;
  border-radius: 10px;
}
div:before {
  content: "";
  position: absolute;
  top: 15%;
  left: 100%;
  height: 20px;
  width: 20px;
  background: gray;
  box-sizing:border-box;
  transform: rotate(45deg) translate(-50%);
  border-top: inherit;
  border-right: inherit;
  box-shadow: inherit;
  transition:all 0.8s;
}
div:hover:before{
  top:80%;
  }
&lt;div&gt;Only Element&lt;/div&gt;

想要一个不同的三角形吗?

div {
  height: 100px;
  width: 200px;
  background: gray;
  position: relative;
  cursor: pointer;
  border-radius: 10px;
}
div:before {
  content: "";
  position: absolute;
  top: 70%;
  left: 100%;
  height: 20px;
  width: 20px;
  background: gray;
  box-sizing:border-box;
  transform:  translate(-50%) skewX(45deg);
  box-shadow: inherit;
  transition:all 0.8s;
  z-index:-1;
}
div:hover:before{
  transform:  translate(-50%);
  border-radius:50%;
  top:20%;
  }
&lt;div&gt;Only Element&lt;/div&gt;

【讨论】:

  • 非常详细的答案。
  • 欢呼@Paulie_D:只是认为有一个“详细”答案的空间,可以解决大多数问题:)
  • 我唯一的评论是位置计算值。你能不能不使用transform:translate(50%) 而不是它适用于所有尺寸...不需要幻数?
  • @Paulie_D:你是个天才(忘记了……再次)哈哈。已编辑
【解决方案2】:

我们可以依靠clip-path 和阴影过滤器轻松实现这一点:

.box {
  margin: 50px;
  width: 200px;
  height: 100px;
  border-radius: 15px;
  background: red;
  position: relative;
  filter:  /* the more shadow you add the thicker the border will be */
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green);
}

.box::before {
  content: "";
  position: absolute;
  top: 100%;
  left: 20%;
  height: 30px;
  width: 50px;
  background: inherit;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}

.box:hover {
  background:blue;
}

body {
  background:linear-gradient(to right, pink,grey);
}
&lt;div class="box"&gt;&lt;/div&gt;

我们可以扩展这个基本示例以考虑任何类型的位置和三角形:

.box {
  margin: 30px;
  width: 150px;
  height: 80px;
  display:inline-block;
  border-radius: 15px;
  background: red;
  position: relative;
  filter:  /* the more shadow you add the thicker the border will be */
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green);
}

.box::before {
  content: "";
  position: absolute;
  height: var(--h,20px);
  width: var(--w,30px);
  background: inherit;
  transform:scale(var(--x,1),var(--y,1));
}

.box.p-bottom::before{
  top: 100%;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.box.p-bottom.alt::before{
  clip-path: polygon(0 0, 100% 0, 100% 100%);
}
.box.p-top::before{
  bottom: 100%;
  clip-path: polygon(0 100%, 100% 100%, 50% 0);
}
.box.p-top.alt::before{
  clip-path: polygon(0 100%, 100% 100%, 100% 0);
}
.box.p-left::before{
  right: 100%;
  clip-path: polygon(100% 0, 100% 100%,0% 50%);
}
.box.p-left.alt::before{
  clip-path: polygon(100% 0, 100% 100%,0% 100%);
}
.box.p-right::before{
  left: 100%;
  clip-path: polygon(0% 0, 0% 100%,100% 50%);
}
.box.p-right.alt::before{
  clip-path: polygon(0% 0, 0% 100%,100% 100%);
}

.box.right::before{
  right:var(--p,20px);
}
.box.left::before {
  left:var(--p,20px);
}
.box.top::before{
  top:var(--p,20px);
}
.box.bottom::before {
  bottom:var(--p,20px);
}


.box:hover {
  background:blue;
}

body {
  background:linear-gradient(to right, pink,grey);
}
<div class="box p-bottom right"></div>
<div class="box p-bottom right alt"></div>
<div class="box p-bottom right alt" style="--x:-1"></div>
<div class="box p-top left"></div>
<div class="box p-top right" style="--p:40%"></div>
<div class="box p-top right alt" style="--p:40%"></div>
<div class="box p-left top"></div>
<div class="box p-left top alt"></div>
<div class="box p-right bottom" style="--w:20px;"></div>
<div class="box p-right bottom" style="--p:30px;--w:20px;--h:30px"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--y:-1"></div>

我们还可以为整个形状考虑任何类型的背景。该技巧适用于固定宽度/高度。我们的想法是为主元素和伪元素创建一个大小相同的背景,然后我们只需调整伪元素内部的位置以匹配父元素的位置(以获得完美的重叠)

.box {
  --h:20px;
  --w:30px;
  --p:20px;

  margin: 30px;
  width: 150px;
  height: 80px;
  display:inline-block;
  border-radius: 15px;
  background: 
    var(--back,linear-gradient(45deg,red,purple)) 
    center/
    calc(150px + 2*var(--w)) calc(80px + 2*var(--h));
  position: relative;
  filter:  /* the more shadow you add the thicker the border will be */
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green) 
    drop-shadow(0px 0px 1px green);
}

.box::before {
  content: "";
  position: absolute;
  height: var(--h);
  width: var(--w);
  background: inherit;
  transform:scale(var(--x,1),var(--y,1));
  background-position:var(--b1) 0 var(--b2);
}

.box.p-bottom::before{
  top: 100%;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  --b1:bottom;
}
.box.p-bottom.alt::before{
  clip-path: polygon(0 0, 100% 0, 100% 100%);
}
.box.p-top::before{
  bottom: 100%;
  clip-path: polygon(0 100%, 100% 100%, 50% 0);
  --b1:top;
}
.box.p-top.alt::before{
  clip-path: polygon(0 100%, 100% 100%, 100% 0);
}
.box.p-left::before{
  right: 100%;
  clip-path: polygon(100% 0, 100% 100%,0% 50%);
  --b1:left;
}
.box.p-left.alt::before{
  clip-path: polygon(100% 0, 100% 100%,0% 100%);
}
.box.p-right::before{
  left: 100%;
  clip-path: polygon(0% 0, 0% 100%,100% 50%);
  --b1:right;
}
.box.p-right.alt::before{
  clip-path: polygon(0% 0, 0% 100%,100% 100%);
}

.box.right::before{
  right:var(--p);
  --b2:right calc(-1*var(--p) - var(--w)); 
}
.box.left::before {
  left:var(--p);
  --b2:left  calc(-1*var(--p) - var(--w));
}
.box.top::before{
  top:var(--p);
  --b2:top  calc(-1*var(--p) - var(--h));
}
.box.bottom::before {
  bottom:var(--p);
  --b2:bottom  calc(-1*var(--p) - var(--h));
}

body {
  background:linear-gradient(to right, pink,grey);
}
<div class="box p-bottom right"></div>
<div class="box p-bottom right alt" style="--back:url(https://picsum.photos/id/15/400/300)"></div>
<div class="box p-bottom right alt" style="--x:-1;--back:red"></div>
<div class="box p-top left" style="--back:url(https://picsum.photos/id/18/400/300)"></div>
<div class="box p-top right" style="--p:40px;--back:url(https://picsum.photos/id/1018/400/300)"></div>
<div class="box p-top right alt" style="--p:60px;--back:radial-gradient(red,pink,yellow)"></div>
<div class="box p-left top" style="--back:black"></div>
<div class="box p-left top alt" style="--back:repeating-linear-gradient(45deg,#fff 0 10px,orange 0 20px)"></div>
<div class="box p-right bottom" style="--w:20px;--back:linear-gradient(red,pink,yellow)"></div>
<div class="box p-right bottom" style="--p:30px;--w:20px;--h:30px;--back:repeating-radial-gradient(#fff 0 10px,orange 0 20px)"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--back:conic-gradient(red,pink,yellow,red)"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--y:-1;"></div>

【讨论】:

  • 干杯 Temani。希望现在有了所有这些选项,我们将减少 css-shapes 标签上的“如何制作演讲泡沫”问题!
【解决方案3】:

SVG

这没有通过命中测试,因为透明边框也是可点击的

这可以使用 svg 中的指针事件来完成。
pointer-events:visibleFill; 只会选择有油漆的部分。

此示例使用 filter_box-shadow,IE 不支持。
也使用两种形状。

html,
body {
  margin: 0;
  padding: 0;
}
.bubble {
  width: 150px;
  height: 150px;
  -webkit-filter: drop-shadow(5px 5px 0px #aaa);
  filter: drop-shadow(5px 5px 0px #aaa);
}
.bubble-shape {
  fill: #1e1;
}
.shape-text {
  color: black;
}
<svg class="bubble" viewBox="0 0 110 110" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
  <g class="bubble-shape" style="cursor:pointer; pointer-events:visibleFill;">
    <rect x="10" y="10" width="90" height="90" rx="15" ry="15" />
    <polygon points="20,94 40,94 30,105" />
  </g>
</svg>

此示例使用一个路径
IE应该完全支持。

html,
body {
  margin: 0;
  padding: 0;
}
.bubble {
  width: 150px;
  height: 150px;
}
.bubble-shape {
  stroke-width: 15;
  stroke: #ddd;
  fill: #1e1;
}
.shape-text {
  color: black;
}
<svg class="bubble" viewBox="-70 -10 390 370" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
  <g style="cursor:pointer; pointer-events:visible;">
    <path class="bubble-shape" d="m 0,0 250,0 c 25,0 50,20 50,50 l 0,225 c 0,25 -25,50 -50,50 l -175,0 -25,20 -20,-20 -40,0 c -25,0 -50,-25 -50,-50 l 0,-225 C -50,25 -50,0 0,0 Z" />
  </g>
</svg>

【讨论】:

    猜你喜欢
    • 2021-01-15
    • 2018-04-14
    • 2011-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-16
    • 2015-07-25
    • 1970-01-01
    相关资源
    最近更新 更多