CSS “inverse border-radius” outside element's bounding box to create a mobile phone notch design [duplicate]

旧时模样 提交于 2020-01-05 07:07:09

问题


I'm trying to create something that looks like a mobile phone with HTML and CSS and I'd like the camera to have something like an "inverted border-radius" that connects it with the frame smoothly.

I can't just make it bigger and mask the unwanted area with a pseudoelement with a white background because the screen content might not always be white.

Also, I can't use mask-image on that same element because that "inverted border-radius" would actually extend past it's bounding box, so I would actually be adding more area rather than subtracting (plus support is really low).

I want to avoid using SVGs if possible.

body {
  position: relative;
  overflow: hidden;
  height: 100vh;
  margin: 0;
}

.phone {
  width: 420px;
  height: 800px;
  padding: 12px 12px 24px;
  position: absolute;
  top: 32px;
  left: 50%;
  transform: translate(-50%, 0);
  background: #000;
  box-shadow: 0 8px 32px 0 rgba(0, 0, 0, .125);
  border-radius: 16px;
}

.screen {
  height: 100%;
  overflow: hidden;
  position: relative;
  background: #FFF;
  border-radius: 8px;
}

.viewport {
  height: 100%;
  position: relative;
  overflow-x: hidden;
  overflow-y: scroll;
}

.notch {
  top: 12px;
  left: 50%;
  width: 24px;
  height: 12px;
  z-index: 10;
  position: absolute;
  transform: translate(-50%, 0);
  background: #000;
  border-bottom-left-radius: 1024px;
  border-bottom-right-radius: 1024px;
}

.camera {
  top: 0;
  left: 50%;
  width: 12px;
  border: 4px solid #33244A;
  height: 12px;
  position: absolute;
  transform: translate(-50%, -50%);
  background:  #304A58;
  border-radius: 1024px;
  box-sizing: border-box;
}
<div class="phone">
  <div class="notch">
    <div class="camera"></div>
  </div>

  <div class="screen">
    <div class="viewport"></div>
  </div>
</div>

回答1:


There are four ways to do that, from simple to more complex:

  • Adding 2 pseudoelements with radial-gradient.

    Simplest and well-supported solution. Probably the one I would use.

  • Adding 2 pseudoelements with mask-image (same as above, but with worse support).

    Quite similar, code-wise, to the previews one, but with really bad support (needs browser prefixes for those that support it).

  • Adding 2 pseudoelements with a border-radius, box-shadow and background: transparent.

    Needs a bit more code, but it looks a bit smoother, at least on Chrome Version 78.0.3904.108, so maybe it's worth it for you, although the difference is minimal. In any case, the shapes you can do can't be as complex as with the previous alternatives, especially if you want to work with ellipses rather than circles, like in this other question: https://stackoverflow.com/a/59278227/3723993.

  • Using an SVG.

    I think the SVG solution is not worth it here, but it would be a good alternative for more complex shapes or animated/transitioning shapes.

Here you can check the first 3 solutions:

const notch = document.getElementById('notch');
const button = document.getElementById('button');
const xrayCheckbox = document.getElementById('xrayCheckbox');
const xrayLabel = document.getElementById('xrayLabel');
const label = document.getElementById('label');

const solutions = [{
  name: 'pseudoelements + radial-gradient',
  classes: 'notch notch-gradient'
}, {
  name: 'pseudoelements + box-shadow',
  classes: 'notch notch-shadow'
}, {
  name: 'pseudoelements + mask-image',
  classes: 'notch notch-mask'
}];

let currentSolutionIndex = 0;
let currentSolution = solutions[currentSolutionIndex];
let xRayEnabled = false;

button.onclick = () => {
  currentSolutionIndex = (currentSolutionIndex + 1) % solutions.length;
  currentSolution = solutions[currentSolutionIndex];
  
  updateLabels();
};

xrayCheckbox.onchange = () => {  
  xRayEnabled = xrayCheckbox.checked;
  
  updateLabels();
};

function updateLabels() {
  if (xRayEnabled) {
    notch.className = `${ currentSolution.classes }-xray`;
    label.innerText = `${ currentSolution.name } (X-Ray)`;
    xrayLabel.innerText = 'Disable X-Ray';
  } else {
    notch.className = currentSolution.classes;
    label.innerText = currentSolution.name;
    xrayLabel.innerText = 'Enable X-Ray';
  }
}
body {
  position: relative;
  overflow: hidden;
  height: 100vh;
  margin: 0;
}

.phone {
  width: 420px;
  height: 800px;
  padding: 12px 12px 24px;
  position: absolute;
  top: 32px;
  left: 50%;
  transform: translate(-50%, 0);
  background: #000;
  box-shadow: 0 8px 32px 0 rgba(0, 0, 0, .5);
  border-radius: 16px;
}

.screen {
  height: 100%;
  overflow: hidden;
  position: relative;
  background: #FFF;
  border-radius: 8px;
}

.viewport {
  height: 100%;
  position: relative;
  overflow-x: hidden;
  overflow-y: scroll;
}

.notch {
  top: 12px;
  left: 50%;
  width: 24px;
  height: 12px;
  z-index: 10;
  position: absolute;
  transform: translate(-50%, 0);
  background: #000;
  border-bottom-left-radius: 1024px;
  border-bottom-right-radius: 1024px;
}

.notch::before,
.notch::after {
  top: 0;
  width: 8px;
  height: 8px;
  content: "";
  position: absolute;
}

.notch-gradient-xray,
.notch-shadow-xray,
.notch-mask-xray {
  background: red;
}

/* RADIAL GRADIENT SOLUTION */

.notch-gradient::before {
  left: -6px;
  background: radial-gradient(circle at bottom left, transparent 0, transparent 70%, black 70%, black 100%);
}

.notch-gradient::after {
  right: -6px;
  background: radial-gradient(circle at bottom right, transparent 0, transparent 70%, black 70%, black 100%);
}

.notch-gradient-xray::before {
  left: -6px;
  background: green radial-gradient(circle at bottom left, transparent 0, transparent 70%, cyan 70%, cyan 100%);
}

.notch-gradient-xray::after {
  right: -6px;
  background: green radial-gradient(circle at bottom right, transparent 0, transparent 70%, cyan 70%, cyan 100%);
}

/* BOX-SHADOW SOLUTION */

.notch-shadow::before {
  left: -6px;
  background: transparent;
  border-radius: 0 8px 0 0;
  box-shadow: 0 -4px 0 0 #000;
}

.notch-shadow::after {
  right: -6px;
  background: transparent;
  border-radius: 8px 0 0 0;
  box-shadow: 0 -4px 0 0 #000;
}

.notch-shadow-xray::before {
  left: -6px;
  background: green;
  border-radius: 0 8px 0 0;
  box-shadow: 0 -4px 0 0 cyan;
}

.notch-shadow-xray::after {
  right: -6px;
  background: green;
  border-radius: 8px 0 0 0;
  box-shadow: 0 -4px 0 0 cyan;
}

/* MASK SOLUTION */

.notch-mask::before {
  left: -6px;
  background: #000;
  -webkit-mask-image: radial-gradient(circle at bottom left, transparent 0, transparent 70%, black 70%, black 100%);
}

.notch-mask::after {
  right: -6px;
  background: #000;
  -webkit-mask-image: radial-gradient(circle at bottom right, transparent 0, transparent 70%, black 70%, black 100%);
}

.notch-mask-xray::before {
  left: -6px;
  background: cyan;
  -webkit-mask-image: radial-gradient(circle at bottom left, transparent 0, transparent 70%, black 70%, black 100%);
}

.notch-mask-xray::after {
  right: -6px;
  background: cyan;
  -webkit-mask-image: radial-gradient(circle at bottom right, transparent 0, transparent 70%, black 70%, black 100%);
}

.camera {
  top: 0;
  left: 50%;
  width: 12px;
  border: 4px solid #33244A;
  height: 12px;
  position: absolute;
  transform: translate(-50%, -50%);
  background:  #304A58;
  border-radius: 1024px;
  box-sizing: border-box;
}

#button {
  font-family: monospace;
  font-size: 16px;
  padding: 8px 16px;
  margin: 32px auto 16px;
  background: transparent;
  border: 2px solid black;
  display: block;
  border-radius: 2px;
}

#xray {
  font-family: monospace;
  font-size: 16px;
  padding: 0 16px;
  text-align: center;
  display: block;
  margin: 0 0 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}

#xrayCheckbox {
  margin: 0 8px 0 0;
}

#label {
  font-family: monospace;
  font-size: 16px;
  padding: 0 16px;
  text-align: center;
}
<div class="phone">
  <div id="notch" class="notch notch-gradient">
    <div class="camera"></div>
  </div>

  <div class="screen">
    <div class="viewport">
      <button id="button">Change Solution</button>
      
      <label id="xray">
        <input id="xrayCheckbox" type="checkbox" />
        <span id="xrayLabel">Enable X-Ray</span>
      </label>
      
      <div id="label">pseudoelements + radial-gradient</div>
    </div>
  </div>
</div>


来源:https://stackoverflow.com/questions/59277465/css-inverse-border-radius-outside-elements-bounding-box-to-create-a-mobile-ph

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!